;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: User; -*-
(in-package :cl-user)

;;;; CVT-LOOP and friends

;;; Code taken from Norvig's PAIP, modified to generate Dylan instead of 
;;; Lisp, and augmented to handle more loop clauses, and to handle loop 
;;; symbols in different packages.

(defstruct loops ;; LOOPS stands for LOOP Structure (can't use LOOP).
  "A structure to hold parts of a loop as it is built."
  (code nil)     ;; The original complete Lisp code
  (exps nil)     ;; What remains to be parsed
  (vars nil)     ;; List of (var val), in Lisp (reversed)
  (fors nil)     ;; List of DYLAN code
  (body nil)     ;; List of Lisp code (reversed)
  (prologue nil) ;; List of Lisp code (reversed)
  (epilogue nil) ;; List of Lisp code (reversed)
  (result nil)   ;; The final result (Lisp)
  (name nil)     ;; Name for return-from
  (conditionals nil)) ;; Stack of currently active when/unless's

(defun cvt-loop (exp)
  "Supports both ANSI and simple LOOP. Warning: Not all of LOOP is supported."
  (if (listp (strip (first/ (args exp))))
      ;; No keyword implies simple loop:
      (handle-returns1 `(while |\#t| ,@(cvt-body (args exp))))
    ;; otherwise process loop keywords:
    (let ((l (make-loops :code exp :exps (args exp))))
      (CATCH 'LOOP-ERROR
	     (loop (if (null (loops-exps l)) (RETURN))
	       (parse-clause l))
	     (fill-loop-template l)))))

(defun parse-clause (l)
  (let ((key (pop (loops-exps l))))
    (funcall (get-loop-fn key) l key (pop (loops-exps l)))))

(defun fill-loop-template (l)
  "Use a loops-structure instance to fill the template."
  (let ((code (handle-returns1
	       `(for (:list-bare ,@(nreverse (loops-fors l)))
		     ,@(cvt-exps (nreverse (loops-body l)))
		     ,@(if (or (loops-epilogue l) (loops-result l))
			   `((:finally ,@(cvt-exps (nreverse (loops-epilogue l)))
				       ,(cvt-exp (loops-result l))))))
	       (loops-name l))))
    (loop for (var val) in (loops-vars l) do
	  (setf code `(let ,(cvt-exp var) ,(cvt-exp val) ,code)))
    (when (loops-prologue l)
      (setf code `(begin ,@(cvt-exps (nreverse (loops-prologue l))) ,code)))
    (encapsulate-let code)))

(defun loop-error (l key exp)
  (cond ((get-option :macroexpand-hard-loops)
	 (let ((expanded (handle-loop-finish (safe-macroexpand (loops-code l)))))
	   (warn "Can't handle ~A ~A in loop; macroexpanding." key exp)
	   (THROW 'LOOP-ERROR (cvt-exp expanded))))
	(t (push (cvt-erroneous (loops-code l) nil
				"Can't handle ~A ~A in loop." key exp)
		 (loops-body l)))))


(defvar *loop-fns* (make-hash-table :test #'equal))

(defun get-loop-fn (key)
  (let ((sym (strip key)))
    (or (and (symbolp sym) (gethash (string sym) *loop-fns*))
	'loop-error)))

(defmacro def-loop (keys (l next-exp &optional (key-var 'key)) &rest body)
  "Define a new LOOP keyword or keywords."
  `(setf ,@(mapcan
	    #'(lambda (key)
		`((gethash ,(string key) *loop-fns*)
		  #'(lambda (,l ,key-var ,next-exp) 
		      (declare (ignore ,key-var))
		      ,@body)))
	    (mklist keys))))

(defun add-var (l var init)
  "Add a variable to the loop."
  (unless (assoc/ (strip var) (loops-vars l))
    (push (list var init) (loops-vars l))))

(defun handle-loop-finish (exp)
  (if (and (starts-with exp 'macrolet) (length=1 (second/ exp))
	   (eq 'loop-finish (first/ (first/ (second/ exp)))))
      (subst (third (first/ (second/ exp))) '(loop-finish) 
	     `(progn ,@(cddr exp))
	     :test #'equal)
      exp))
      
(defun parse-loop-key (l &rest keys)
  "If the next exp in L is one of keys, pop it and return true."
  (when (apply #'loop=? (first/ (loops-exps l)) keys)
    (pop (loops-exps l))
    t))

(defun loop=? (exp &rest options)
  ;; Is exp a symbol that is spelled the same as one of the options?
  (and (symbolp (strip exp))
       (member (strip exp) options :test #'string-equal)))

;;;; Loop Clauses 26.6 (p 716 CLtL2) Iteration Control

(def-loop (FOR AS) (l var)
  (when (not (symbolp (strip var)))
    (loop-error l 'for var))
  (setq var (parse-var l var))
  (cond
   ((parse-loop-key l "IN" "ACROSS")
    (push `(:for-clause ,var in ,(cvt-exp (pop (loops-exps l)))) 
	  (loops-fors l)))
   ((parse-loop-key l "ON")
    (let ((by (if (parse-loop-key l "BY")
		  (cvt-exp (pop (loops-exps l)))
		'tail)))
      (push `(:for-clause ,var = ,(cvt-exp (pop (loops-exps l))) 
			  then (,by ,var))
	    (loops-fors l))
      (push `(:for-clause :until (empty? ,var)) (loops-fors l))))
   ((parse-loop-key l "BEING")
    (parse-loop-key l "EACH" "THE")
    (cond ((parse-loop-key l "HASH-VALUE" "HASH-VALUES")
	   (push `(:for-clause ,var in ,(cvt-exp (pop (loops-exps l))))
		 (loops-fors l)))
	  ((parse-loop-key l "HASH-KEY" "HASH-KEYS")
	   (push `(:for-clause ,var in 
			       (key-sequence ,(cvt-exp (pop (loops-exps l)))))
		 (loops-fors l)))
	  (t (loop-error l 'for var))))
   ((parse-loop-key l "=")
    (let* ((init (cvt-exp (pop (loops-exps l))))
	   (next (if (parse-loop-key l "THEN")
		     (cvt-exp (pop (loops-exps l)))
		   init)))
      (push `(:for-clause ,var = ,init then ,next) (loops-fors l))))
   (t (try-loop-for-arithmetic l var))))

(defun try-loop-for-arithmetic (l var)
  (let ((start 0)
	(to nil)
	(by nil)
        (negative? nil))
    (loop (let ((subkey (first/ (loops-exps l))))
	    (cond ((loop=? subkey "TO" "BELOW" "ABOVE")
                   (when (loop=? subkey "ABOVE") (setq negative? t))
		   (pop (loops-exps l))
		   (setf to (list subkey (cvt-exp (pop (loops-exps l))))))
                  ((loop=? subkey "UPTO" "DOWNTO") ;; Convert to TO
                   (when (loop=? subkey "DOWNTTO") (setq negative? t))
                   (pop (loops-exps l))
                   (push 'to (loops-exps l)))
                  ((loop=? subkey "FROM" "DOWNFROM" "UPFROM")
                   (when (loop=? subkey "DOWNFROM") (setq negative? t))
                   (pop (loops-exps l))
                   (setq start (cvt-exp (pop (loops-exps l)))))
		  ((loop=? subkey "BY")
		   (pop (loops-exps l))
		   (setf by (list subkey (cvt-exp (pop (loops-exps l))))))
		  (t (RETURN)))))
    ;; A bit tricky here: Lisp's BY clause are always positive,
    ;; Dylan's are negative for decrement.  E.g., we want
    ;; (loop for x downfrom 10 to 0 by 2) => for(x from 10 to 0 by -2)
    ;; (loop for x downfrom 10 to 0)      => for(x from 10 to 0 by -1)
    ;; (loop for x downfrom 10 above 0)   => for(x from 10 above 0)
    (when negative?
      (cond (by (setf (second by) `(-- ,(second by))))
            ((null to) (setf by '(by -1)))
	    ((loop=? (first to) "ABOVE") 'ignore)
            (t (setf by '(by -1)))))
    (push `(:for-clause ,var from ,start ,@to ,@by) (loops-fors l))))

(def-loop repeat (l times)
  "(LOOP REPEAT n ...) does loop body n times" 
  (push `(:for-clause _ from 1 to ,(cvt-exp times)) (loops-fors l)))

;;;; Loop Clauses 26.7 End-Test Control 

(def-loop while (l test) 
  (push `(:for-clause :while ,(cvt-exp test)) (loops-fors l)))

(def-loop until (l test) 
  (push `(:for-clause :until ,(cvt-exp test)) (loops-fors l)))

(def-loop always (l test)
  (setf (loops-result l) 't)
  (add-body l `(if (not ,test) (return 'nil))))

(def-loop never (l test)
  (setf (loops-result l) 't)
  (add-body l `(if ,test (return 'nil))))

(def-loop thereis (l test) 
  (setf (loops-result l) 'nil)
  (add-var l '_ nil)
  (add-body l `(if (setq _ ,test (return _)))))

;;;; Loop Clauses 26.8 Value Accumulation

(def-loop (collect collecting) (l exp)
  (accumulate l exp '(make <deque>) '(push-last INTO VAL)))

(def-loop (nconc nconcing) (l exp) 
  (accumulate l exp '|()| '(setq INTO (nconc INTO VAL))))
(def-loop (append appending) (l exp) 
  (accumulate l exp '|()| '(setq INTO (append INTO VAL))))

(def-loop (count counting) (l exp) (accumulate l exp 0 '(if VAL (incf INTO))))

(def-loop (sum summing) (l exp)  (accumulate l exp 0 '(incf INTO VAL)))

(def-loop (maximize maximizing) (l exp) 
  (accumulate l exp 'nil '(setq INTO (if INTO (max INTO VAL) VAL))))
(def-loop (minimize minimizing) (l exp)  
  (accumulate l exp 'nil '(setq INTO (if INTO (min INTO VAL) VAL))))

(defun accumulate (l val init form)
  (let ((into '_acc))
    (when (parse-loop-key l "INTO")
      (setq into (pop (loops-exps l))))
    (when (null (loops-result l))
      (setf (loops-result l) into))
    (add-var l into init)
    (add-body l (sublis `((INTO . ,into) (VAL . ,val)) form))))

;;;; 26.9. Variable Initializations

(def-loop with (l var)
  (let ((vars nil) (vals nil))
    (push var (loops-exps l))
    (loop 
	(push (parse-var l) vars)
      (push (if (parse-loop-key l "=") (pop (loops-exps l)) '|\#f|) vals)
      (unless (parse-loop-key l "AND") (RETURN)))
    (cond ((= (length vars) 1)
	   (add-var l (first/ vars) (first/ vals)))
	  (t (add-var l `(:args ,@vars) `(values ,@vals))))))

(defun parse-var (l &optional given-var)
  "Parse and return var [type-spec]" ; See CLtL2 p. 743
  (let ((var (or given-var 
		 (if (symbolp (first/ (loops-exps l))) (pop (loops-exps l))))))
    (if (or (parse-loop-key l "OF-TYPE")
	    (every #'numeric-type? (mklist (first (loops-exps l)))))
	`(|::| ,var ,(cvt-type (pop (loops-exps l))))
      var)))

(defun numeric-type? (x) 
  (member (strip x) '(fixnum float t nil)))

;;;; 26.10 Conditional Execution

;;; This is a little tricky.  We keep a stack of conditionals in each
;;; loop structure, and make sure that add-body puts code into these
;;; when appropriate.

(defun add-body (l exp) 
  (if (loops-conditionals l)
      (let ((target  (first (loops-conditionals l))))
	;; Target is of form (if ... (progn ...)),
	;; or (if ... (progn ...) (progn ...)) within an ELSE.
	;; So we NCONC onto the last (progn ...)
	(nconc1 (last1 target) exp))
    (push exp (loops-body l))))

(def-loop (when if unless) (l test key) 
  ;; WHEN expr clauses [ELSE clauses] [END]
  ;; clauses -> clause {AND clause}*
  (let ((target (list 'if 
		      (if (loop=? key "UNLESS") `(not ,test) test)
		      (list 'progn))))
    (add-body l target)
    (push target (loops-conditionals l))
    (parse-clauses l)
    (when (parse-loop-key l "ELSE")
      (setf (first (loops-conditionals l))
	    (nconc1 (first (loops-conditionals l)) (list 'progn)))
      (parse-clauses l))
    (pop (loops-conditionals l))
    (parse-loop-key l "END")))   

(defun parse-clauses (l)
  ;; Conditional clauses are either:
  ;; collect/append/sum/count/minimize/maximize do/return when/unless/else
  ;; But we don't make that restriction; we parse any clause.
  (parse-clause l)
  (when (parse-loop-key l "AND") (parse-clauses l)))

(defun maybe-set-it (test exps)
  "Return value, but if the variable IT appears in exps,
  then return code that sets IT to value."
  (if (find-anywhere 'it exps)
      `(setq it ,test)
      test))

;;;; 26.11 Unconditional Execution

(def-loop (do doing) (l exp)
  (add-body l exp)
  (loop (if (symbolp (first/ (loops-exps l))) (RETURN))
        (add-body l (pop (loops-exps l)))))

(def-loop return (l exp) 
  (add-body l `(return ,exp)))

;;;; 26.12 Miscellaneous Features

(def-loop initially (l exp)
  (push exp (loops-prologue l))
  (loop (if (symbolp (first/ (loops-exps l))) (RETURN))
        (push (pop (loops-exps l)) (loops-prologue l))))

(def-loop finally (l exp)
  (push exp (loops-epilogue l))
  (loop (if (symbolp (first/ (loops-exps l))) (RETURN))
        (push (pop (loops-exps l)) (loops-epilogue l))))

(def-loop named (l exp) (setf (loops-name l) exp))






                  
