"cafe.ss"
;;; cafe.ss
;;; Copyright 1984-2017 Cisco Systems, Inc.
;;; 
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;; 
;;; http://www.apache.org/licenses/LICENSE-2.0
;;; 
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(define default-prompt-and-read
  (lambda (n)
    (unless (and (integer? n) (>= n 0))
       ($oops 'default-prompt-and-read
              "~s is not a nonnegative integer"
              n))
    (let ([prompt (waiter-prompt-string)])
      (unless (string=? prompt "")
        (do ([n n (- n 1)])
            ((= n 0)
             (write-char #\space (console-output-port))
             (flush-output-port (console-output-port)))
            (display prompt (console-output-port))))
      (let ([x (read (console-input-port))])
         (when (and (eof-object? x) (not (string=? prompt "")))
            (newline (console-output-port))
            (flush-output-port (console-output-port)))
         x))))

(define waiter-prompt-and-read
   ($make-thread-parameter
      default-prompt-and-read
      (lambda (p)
         (unless (procedure? p)
            ($oops 'waiter-prompt-and-read "~s is not a procedure" p))
         p)))

(define waiter-write
   ($make-thread-parameter
      (lambda (x)
         (unless (eq? x (void))
            (pretty-print x (console-output-port)))
         (flush-output-port (console-output-port)))
      (lambda (p)
         (unless (procedure? p)
            ($oops 'waiter-write "~s is not a procedure" p))
         p)))

(define waiter-prompt-string
   ($make-thread-parameter
      ">"
      (lambda (s)
         (unless (string? s)
            ($oops 'waiter-prompt-string "~s is not a string" s))
         s)))

(define new-cafe)

(let ()
(define-threaded waiter-expr)
(define-threaded waiter-stat1)
(define-threaded waiter-stat2)
(define-threaded waiter-total-stats)

(define sstats-sum
  (lambda (a b)
    (define sstats-time-add
      (lambda (f a b)
        (add-duration (f a) (f b))))
    (make-sstats
      (sstats-time-add sstats-cpu a b)
      (sstats-time-add sstats-real a b)
      (+ (sstats-bytes a) (sstats-bytes b))
      (+ (sstats-gc-count a) (sstats-gc-count b))
      (sstats-time-add sstats-gc-cpu a b)
      (sstats-time-add sstats-gc-real a b)
      (+ (sstats-gc-bytes a) (sstats-gc-bytes b)))))

(define waiter
   (lambda (cafe eval)
      (let ([x ((waiter-prompt-and-read) cafe)])
         (when (eof-object? x) (exit))
         (fluid-let ([waiter-total-stats (make-sstats
                                           (make-time 'time-duration 0 0)
                                           (make-time 'time-duration 0 0)
                                           0
                                           0
                                           (make-time 'time-duration 0 0)
                                           (make-time 'time-duration 0 0)
                                           0)]
                     [waiter-expr x]
                     [waiter-stat1 (void)]
                     [waiter-stat2 (void)])
            (dynamic-wind #t
               (lambda ()
                  (set! waiter-stat1 (statistics))
                  (set! waiter-stat2 (statistics)))
               (lambda ()
                  (parameterize ([$interrupt waiter-interrupt])
                     (top-level eval x)))
               (lambda ()
                  (let ([s (statistics)])
                     (set! waiter-total-stats
                           (sstats-sum (sstats-difference
                                          (sstats-difference s waiter-stat2)
                                          (sstats-difference waiter-stat2
                                                             waiter-stat1))
                                       waiter-total-stats)))))))
      (waiter cafe eval)))

; This marks the "top-level" continuation for the debugger
(define top-level
  (lambda (eval x)
    (call/cc ; grab continuation & start a new stack segment
      (rec new-cafe
        (lambda (k)
          ($current-stack-link $null-continuation) ; toss what's below
          (call-with-values
            (lambda () (eval x))
            (lambda args (for-each (waiter-write) args)))
          (k))))))

(define waiter-interrupt
   (lambda ()
      (call/cc
         (lambda (k)
            (parameterize ([$interrupt void])
               (let ([s (statistics)])
                  (set! waiter-total-stats
                        (sstats-sum (sstats-difference
                                       (sstats-difference s waiter-stat2)
                                       (sstats-difference waiter-stat2
                                                          waiter-stat1))
                                    waiter-total-stats)))
               (clear-input-port (console-input-port))
               (let ([waiter (call/cc
                                (lambda (k)
                                   (rec f (lambda () (k f)))))])
                  (fprintf (console-output-port) "break> ")
                  (flush-output-port (console-output-port))
                  (case (let ([x (parameterize ([$interrupt waiter]
                                                [reset-handler waiter])
                                    (read (console-input-port)))])
                           (if (eof-object? x) 
                               (begin (newline (console-output-port))
                                      (flush-output-port (console-output-port))
                                      'exit)
                               x))
                     [(exit e)
                      (void)]
                     [(statistics s)
                      (parameterize ([print-level 2] [print-length 2])
                         (fprintf (console-output-port)
                                  "(time ~s)~%"
                                  waiter-expr))
                      (sstats-print waiter-total-stats (console-output-port))
                      (flush-output-port (console-output-port))
                      (waiter)]
                     [(reset r quit q)
                      (reset)]
                     [(abort a)
                      (abort)]
                     [(new-cafe n)
                      (new-cafe)
                      (waiter)]
                     [(inspect i)
                      (inspect k)
                      (waiter)]
                     [(?)
                      (fprintf (console-output-port) "
Type e to exit interrupt handler and continue
     r or q to reset scheme
     a to abort scheme
     n to enter new cafe
     i to inspect current continuation
     s to display statistics

")
                      (flush-output-port (console-output-port))
                      (waiter)]
                     [else
                      (fprintf (console-output-port)
                               "Invalid command.  Type ? for options.~%")
                      (flush-output-port (console-output-port))
                      (waiter)]))
               (set! waiter-stat1 (statistics))
               (set! waiter-stat2 (statistics)))))))

(set! $cafe ($make-thread-parameter 0))

(set! new-cafe
  (let ()
    (rec new-cafe
      (case-lambda
        [() (new-cafe eval)]
        [(eval)
         (unless (procedure? eval)
           ($oops 'new-cafe "~s is not a procedure" eval))
         (call/cc
           (lambda (k1)
             (parameterize ([exit-handler k1] [reset-handler (reset-handler)])
               (let ((k2 k1))
                 (reset-handler (lambda () (k2)))
                 (call/cc (lambda (k) (set! k2 k)))
                 (parameterize ([$cafe (+ ($cafe) 1)] [$interrupt reset])
                   (with-exception-handler
                     (lambda (c) ((base-exception-handler) c))
                     (lambda ()
                       (waiter ($cafe) eval))))))))]))))
)
