;; Nada Amin (namin@mit.edu)
;; 6.945 Problem Set 8
;; Due: Wed. 16 Apr. 2008

;(load "../test-manager/load.scm")
(load "load.scm")

;; Problem 8.1
#|

Let N be the sum of the depth of all terminal leaves. Then the fringe
procedure takes O(N) time and space. Indeed, for each terminal leaf,
append might copy it at most a number of times proportional to its
depth.

|#

;; Problem 8.2

;; A.
#|

If we had not thunkified the second argument to be appended, then
(lazy-fringe (cdr subtree)) would be executed non-lazily recursively, and so
we would be doing much more work in advance than necessary.

|#

;; B.

(define (lazy-fringe subtree)
  (define (walk subtree thunk)
    (cond ((pair? subtree)
           (walk (car subtree)
                 (lambda ()
                   (walk (cdr subtree)
                         thunk))))
          ((null? subtree)
           (thunk))
          (else (cons-stream subtree (thunk)))))
  (walk subtree (lambda () '())))

(define (lazy-same-fringe? tree1 tree2)
  (let lp ((f1 (lazy-fringe tree1))
           (f2 (lazy-fringe tree2)))
    (cond ((and (stream-null? f1) (stream-null? f2)) #t)
          ((or  (stream-null? f1) (stream-null? f2)) #f)
          ((eq? (stream-car   f1) (stream-car   f2))
           (lp  (stream-cdr   f1) (stream-cdr   f2)))
          (else #f))))
#|
(lazy-same-fringe? '((a b) c ((d)) e (f ((g h))))
                   '(a b c ((d) () e) (f (g (h)))))
;Value: #t

(lazy-same-fringe? '((a b) c ((d)) e (f ((g h))))
                   '(a b c ((d) () e) (g (f (h)))))
;Value: #f
|#

;; Problem 8.3

#|

It is necessary to use the expression "(lambda () (resume-thunk))"
rather than just "resume-thunk" as the returned value of the fringe
generator, so that "(set! resume-thunk continue)" affects the returned
value.

|#

;; Problem 8.4

#| 

If we accidentally left out the return when done, we would be messing
up the coroutine mechanism at the end. We would just keep resuming the
thunk from the same old continuation, without updating it, so the
thunk would ignore its new context totally.

Example:
(define x (make-coroutine (lambda (return) (lambda () (return 'bar) 'foo))))
(x)
; Value: bar
(list (x) (x))
; Value: foo, instead of (foo foo)

#|

;; Problem 8.5

(define (make-pipe)
  (define lock (conspire:make-lock))
  (define queue (queue:make))
  (define (writer value)
    (conspire:acquire-lock lock)
    (queue:add-to-end! queue value)
    (conspire:unlock lock)
    value)
  (define (reader)
    (conspire:acquire-lock lock)
    (if (queue:empty? queue)
        (begin
          (conspire:unlock lock)
          (conspire:switch-threads
           (lambda () (not (queue:empty? queue))))
          (reader))
        (let ((value (queue:get-first queue)))
          (conspire:unlock lock)
          value)))
  (list writer reader))

(define (pipe-writer pipe) (car pipe))

(define (pipe-reader pipe) (cadr pipe))

(define *done* (list '*done*))

(define (piped-same-fringe? tree1 tree2)
  (let ((p1 (make-pipe)) (p2 (make-pipe)))
    (let ((thread1
           (conspire:make-thread
            conspire:runnable
            (lambda ()
              (piped-fringe-generator tree1 (pipe-writer p1)))))
          (thread2
           (conspire:make-thread
            conspire:runnable
            (lambda ()
              (piped-fringe-generator tree2 (pipe-writer p2)))))
          (f1 (pipe-reader p1))
          (f2 (pipe-reader p2)))
      (let lp ((x1 (f1)) (x2 (f2)))
        (cond ((and (eq? x1 *done*) (eq? x2 *done*)) #t)
              ((or  (eq? x1 *done*) (eq? x2 *done*)) #f)
              ((eq? x1 x2) (lp (f1) (f2)))
              (else #f))))))

(define (piped-fringe-generator tree return)
  (define (lp tree)
    (cond ((pair? tree)
           (lp (car tree))
           (lp (cdr tree)))
          ((null? tree) unspecific)
          (else
           (return tree))))
  (lp tree)
  (return *done*))

#|
(define (print-same-fringe tree1 tree2)
  (if (piped-same-fringe? tree1 tree2)
      (write-line `(same fringe ,tree1 ,tree2))
      (write-line `(diff fringe ,tree1 ,tree2))))

(with-time-sharing-conspiracy
 (lambda ()
   (conspire:make-thread 
    conspire:runnable
    (lambda ()
      (print-same-fringe '((a b) c ((d)) e (f ((g h))))
                         '(a b c ((d) () e) (f (g (h)))))))
   (conspire:make-thread 
    conspire:runnable
    (lambda ()
      (print-same-fringe '((a b) c ((d)) e (f ((g h))))
                         '(a b c ((d) () e) (g (f (h)))))))
   (conspire:null-job)))

(same fringe ((a b) c ((d)) e (f ((g h)))) (a b c ((d) () e) (f (g (h)))))
(diff fringe ((a b) c ((d)) e (f ((g h)))) (a b c ((d) () e) (g (f (h)))))
;Value: done
|#

;; Problem 8.6

(define (make-threaded-filter job)
  (let ((pipe (make-pipe)))
    (let ((writer (pipe-writer pipe))
          (reader (pipe-reader pipe)))
      (conspire:make-thread conspire:runnable (lambda () (job writer)))
      reader)))

(define (tf-piped-same-fringe? tree1 tree2)
  (let ((f1 (make-threaded-filter (tf-piped-fringe-generator tree1)))
        (f2 (make-threaded-filter (tf-piped-fringe-generator tree2))))
    (let lp ((x1 (f1)) (x2 (f2)))
      (cond ((and (eq? x1 *done*) (eq? x2 *done*)) #t)
            ((or  (eq? x1 *done*) (eq? x2 *done*)) #f)
            ((eq? x1 x2) (lp (f1) (f2)))
            (else #f)))))

(define (tf-piped-fringe-generator tree)
  (lambda (return)
    (define (lp tree)
      (cond ((pair? tree)
             (lp (car tree))
             (lp (cdr tree)))
            ((null? tree) unspecific)
            (else
             (return tree))))
    (lp tree)
    (return *done*)))

#|
(with-time-sharing-conspiracy
 (lambda ()
   (tf-piped-same-fringe?
    '((a b) c ((d)) e (f ((g h))))
    '(a b c ((d) () e) (f (g (h)))))
   ))
;Value: #t

(with-time-sharing-conspiracy
 (lambda ()
   (tf-piped-same-fringe?
    '((a b) c ((d)) e (f ((g h))))
    '(a b c ((d) () e) (g (f (h)))))
   ))
;Value: #f

(with-time-sharing-conspiracy
 (lambda ()
   (tf-piped-same-fringe?
    '((a b) c ((d)) e (f ((g h))))
    '(a b c ((d) () e) (g (f ))))
   ))
;Value: #f
|#