;;; (SHOW-CONTOUR env who extent)  --- Prints out a contour in a pleasant
;;;     format.  The 2nd argument is used to identify the owner of the
;;;     contour.  The 3rd argument specifies how many bindings constitute
;;;     the contour.  If a closure, it is assumed that the contour was 
;;;     created by expanding this closure.  If a number, that many bindings
;;;     are printed.  Otherwise, a healthy chunk of the (non-global) environment
;;;     is printed. Returns that portion of the environment not shown.
       
(define SHOW-CONTOUR
   (lambda simple [env who extent]
      (cond [(number extent)     ; This many bindings.
             (block
                (p who) (nl)
                (show-bindings env extent))]
            [(closure extent)    ; Pattern will tell us extent of contour.
             (show-contour env who (length (pattern-variables extent)))]
            [$T               ; Show some of it???
             (show-contour env who (min 10 (length (non-global-segment env))))])))



;;; (SHOW-BINDINGS env n)  ---  Prints out the first n variable bindings
;;;     of the given environment.  Returns the tail of the environment 
;;;     that begins with the first binding not printed.  (This procedure
;;;     should probably be local to SHOW-CONTOUR.)
   
(define SHOW-BINDINGS
   (lambda simple [env n]
      (if (or (empty env) (<= n 0))
          env             
          (block
             (ps %"     %") (p (1st (1st env))) (ps %" => %") (p (2nd (1st env))) (nl)
             (show-bindings (rest env) (1- n))))))



;;; @ADVISED-PROCEDURES  ---  Variable designating a sequence of advice records
;;;    so that advice can be undone.  Each entry has the form:
;;;
;;;       [kind-of-advice whos-advice advised-closure original-closure]
;;;    

(set @ADVISED-PROCEDURES (scons))



;;;        *-* Commentary is out of date *-*
;;;
;;; ADVISE   ---  Install advice.  1st argument should be an atom
;;;    indication what kind of advice is being given.  2nd argument 
;;;    should be an atom that identifies who was advised.  3rd
;;;    argument is closure that will be advised (destructively, but
;;;    in an undoable manner).  4th argument is the advice procedure.
;;;    The advice procedure will be called whenever the given closure
;;;    is reduced.   It will be pass 4 arguments.  1. An unadvised version
;;;    of the original closure so that the reduction may be carried out
;;;    unhindered.  2. The normalised or un-normalised arguments of the
;;;    reduction, dependant on whether or not the original closure is simple.
;;;    3.  The environment in effect at the time of the reduction.   4. The
;;;    continuation.
;;; 
;;; e.g. (ADVISE 'TRACE-ENTRY-ADVISE 'foo ↑foo 
;;;          (lambda simple [kind tag proc a e c]
;;;             (block
;;;                (ps %"Entering %") (p tag) (nl)
;;;                (reduce proc a e c))))

(define ADVISE
   (lambda simple [advice-kind tag proc advice]
      (let [[original (closure-copy proc)]]
          (block
             (replace proc (add-advice advice-kind tag original advice))
             (set @advised-procedures
                  (prep [advice-kind tag proc original] @advised-procedures))
             tag))))



;;; ADD-ADVICE  ---  Arrange for advice to be called whenever a 
;;;    particular closure is to be reduced.   PROC designates
;;;    a closure.  ADVICE designates a function of 6 arguments:
;;;    the kind of advice, a tag identifying this piece of advice, 
;;;    the closure being reduced (or copy thereof), the arguments to this 
;;;    reduction, and an environment and continuation.  The result
;;;    designates a closure.  Note that the result will always have
;;;    the same procedure-type (SIMPLE or REFLECT) as the original
;;;    closure.
  
(define ADD-ADVICE
   (lambda simple [advice-kind tag proc advice]
      (if (reflective proc)
          ↑(lambda reflect [a e c]
              (\(add-advice advice-kind tag (de-reflect proc) advice) a e c))
          ↑(lambda simple x
              ((lambda reflect [[] e c]
                  (advice advice-kind tag proc ↑x e c)))))))



(define UNADVISE
   (lambda simple [who-to-unadvise]
      (labels
         [[loop
           (lambda simple [advice-list]
              (cond [(empty advice-list) advice-list]
                    [(= (2nd (1st advice-list)) who-to-unadvise)
                     (block
                        (replace (3rd (1st advice-list)) (4th (1st advice-list)))
                        (p (1st (1st advice-list))) (nl)
                        (loop (rest advice-list)))]
                    [$T
                     (prep (1st advice-list) (loop (rest advice-list)))]))]]
         (set @advised-procedures (loop @advised-procedures)))))



(define ADVICE
   (lambda simple [who]
      (labels
         [[loop
           (lambda simple [advice-list]
              (cond [(empty advice-list) (scons)]
                    [(= (2nd (1st advice-list)) who)
                     (prep (1st advice-list)
                           (loop (rest advice-list)))]
                    [$T (loop (rest advice-list))]))]]
         (map 1st (loop @advised-procedures)))))



(define UNADVISE-ALL
   (lambda simple []
      (block
         (do [[x @advised-procedures (rest x)]]
             [[(empty x) 'ok]]
             (let [[[kind var advised-proc clone] (1st x)]]
                (block
                  (replace advised-proc clone)
                  (p var) (ps %" %") (p kind) (nl))))
         (set @advised-procedures (scons)))))



(define TRACE
   (lambda macro [var]
      ~(trace-both ,↑var ↑,var)))



(define TRACE-BOTH
   (lambda simple [tag closure]
      (block
          (if (reflective closure)
              (reflective-trace-both tag closure)
              (simple-trace-both tag closure)))))



(define SIMPLE-TRACE-BOTH
   (lambda simple [tag closure]
      (block 
         (advise 'TRACE-BOTH-ADVICE
                 tag
                 closure
                 simple-trace-both-advice)
         tag)))



(define REFLECTIVE-TRACE-BOTH
   (lambda simple [tag closure]
      (block
          (cond [(not (rail (pattern closure))) ; Can't find continuation.
                 (simple-trace-both tag closure)]
                [(macro-closure closure) ; Trace expansion of macro
                 (trace-both tag (binding 'expander (environment closure)))]
                [(reflect!-closure closure) ; Trace after args normalised
                 (trace-both tag (binding 'fun (environment closure)))]
                [$T
                 (advise 'TRACE-BOTH-ADVICE
                      tag
                      closure
                      reflective-trace-both-advice)])
          tag)))



(define SIMPLE-TRACE-BOTH-ADVICE
   (lambda simple [advice-kind tag proc! args! env cont]
      (let [[new-env (bind (pattern proc!) args! (environment proc!))]]
        (block
           (show-contour new-env tag proc!)
           (normalise (body proc!) new-env
              (lambda simple [result]
                 (block
                    (p tag) (ps %" returns %") (p result) (nl)
                    (cont result))))))))



(define REFLECTIVE-TRACE-BOTH-ADVICE
   (lambda simple [advice-kind tag proc! args! env cont]
      (let [[new-env (bind (pattern proc!)
                           args!
                           (environment proc!))]]
         (block
            (show-contour new-env tag proc!)
            (rebind (3rd (pattern proc!))
                    (add-advice 'CPS-EXIT-ADVICE
                                ~(,(3rd (pattern proc!)) . ,tag)
                                (binding (3rd (pattern proc!)) new-env)
                                trace-entry-advice)
                    new-env)                        
            (normalise (body proc!) new-env cont)))))



(define MAKE-CPS-TRACE-EXIT-ADVICE
   (lambda simple [cont-var]
      (lambda simple [advice-kind tag proc! args! env cont]
         (let [[new-env (bind (pattern proc!)
                              args!
                              (environment proc!))]]
            (block
               (rebind cont-var
                       (add-advice 'CPS-EXIT-ADVICE
                                   ~(,cont-var OF ,tag)
                                   (binding cont-var new-env)
                                   trace-entry-advice)
                       new-env)                        
               (normalise (body proc!) new-env cont))))))



(define TRACE-ENTRY-ADVICE
   (lambda simple [advice-kind tag proc! args! env cont]
      (let [[new-env (bind (pattern proc!) args! (environment proc!))]]
        (block
           (show-contour new-env tag proc!)
           (normalise (body proc!) new-env cont)))))



(define TRACE-EXIT-ADVICE
   (lambda simple [advice-kind tag proc! args! env cont]
      (reduce proc! args! env
         (lambda simple [result]
            (block                  
               (p tag) (ps %" returns %") (p result) (ps %".%") (nl)
               (cont result))))))



(define BREAK-ENTRY-ADVICE
   (lambda simple [advice-kind tag proc! args! env cont]
      (let [[new-env (bind (pattern proc!) args! (environment proc!))]]
         (block
            (show-contour new-env tag proc!)
            (ps %"Break inside simple procedure.%") (nl)
            (read-normalise-print tag new-env primary-stream)
            (ps %"Break terminated.%") (nl)
            (normalise (body proc!) new-env cont)))))



(define BREAK-EXIT-ADVICE
   (lambda simple [advice-kind tag proc! args! env cont]
      (reduce proc! args! env
         (lambda simple [result]
            (block
               (ps %"About to return.%") (nl)
               (let [[new-env (prep ['result result]
                                    (bind (pattern proc!)
                                          args!
                                          (environment proc!)))]]
                  (block
                     (show-contour new-env tag proc!)
                     (ps %"Breaking inside it.%") (nl)
                     (read-normalise-print tag new-env primary-stream)
                     (ps %"Break terminated.%") (nl)
                     (cont (binding 'result new-env)))))))))



;;; ADVISE-AROUND-BODY  ---  Takes advice in the form of an expression to 
;;;    be normalised instead of the usual body of a particular closure.
;;;    This expression will be normalised in an environment in which the
;;;    atom & is bound to the (lambda-delayed) original body. 
;;;
;;;    Note that this form of advising only works for SIMPLE and REFLECT
;;;    procedures.  Other kinds, such as MACRO or REFLECT!, flop due to
;;;    the fact that the pattern and body of such procedures is not what
;;;    you'd expect if you wrote (LAMBDA REFLECT! {p} {b}). 
;;;
;;; e.g. (ADVISE-AROUND-BODY 'BODY-ADVICE 'fact ↑fact '(if (= n 3) 6 (&)))
;;;

(define ADVISE-AROUND-BODY
   (lambda simple [advice-kind tag proc new-body]
      (let [[clone-of-original (closure-copy proc)]]
          (block
             (replace proc
                      (ccons (procedure-type proc)
                             (environment-designator proc)
                             (pattern proc)
                             ~(let [[& (lambda simple [] ,(body proc))]] ,new-body))) 
             (set @advised-procedures
                  (prep [advice-kind tag proc clone-of-original] @advised-procedures))
             tag))))



;;; ADVISE-BODY  ---  Uses ADVISE-AROUND-BODY to add a single extra statement
;;;    at the start of the procedures body.   Only works properly for (LAMBDA
;;;    SIMPLE ...) and (LAMBDA REFLECT ...) procedures.
;;;
;;; e.g. (ADVISE-BODY 'BODY-ADVICE 'fact ↑fact '(print n primary-stream))
;;;
;;;    would net the same result as redefining FACT as follows:
;;;
;;;    (define FACT
;;;       (lambda simple [x]
;;;          (block (print n primary-stream)
;;;                 (if (= n 0) 1 (* n (fact (1- n)))))))

(define ADVISE-BODY
   (lambda simple [advice-kind tag proc extra-code]
      (advise-around-body advice-kind tag proc ~(block ,extra-code (&)))))