;; swank-r6rs.sls --- Shareable code between swank-ikarus and swank-larceny
;;
;; Licence: public domain
;; Author: Helmut Eller
;;
;; This is a Swank server barely capable enough to process simple eval
;; requests from Emacs before dying.  No fancy features like
;; backtraces, module redefintion, M-. etc. are implemented.  Don't
;; even think about pc-to-source mapping.
;;
;; Despite standard modules, this file uses (swank os) and (swank sys)
;; which define implementation dependend functionality.  There are
;; multiple modules in this files, which is probably not standardized.
;;

;; Naive FORMAT implementation which supports: ~a ~s ~d ~x ~c
(library (swank format)
    (export format printf fprintf)
    (import (rnrs))

 (define (format f . args)
   (call-with-string-output-port
    (lambda (port) (apply fprintf port f args))))

 (define (printf f . args)
   (let ((port (current-output-port)))
     (apply fprintf port f args)
     (flush-output-port port)))

 (define (fprintf port f . args)
   (let ((len (string-length f)))
     (let loop ((i 0) (args args))
       (cond ((= i len) (assert (null? args)))
	     ((and (char=? (string-ref f i) #\~)
		   (< (+ i 1) len))
	      (dispatch-format (string-ref f (+ i 1)) port (car args))
	      (loop (+ i 2) (cdr args)))
	     (else
	      (put-char port (string-ref f i))
	      (loop (+ i 1) args))))))
 
 (define (dispatch-format char port arg)
   (let ((probe (assoc char format-dispatch-table)))
     (cond (probe ((cdr probe) arg port))
	   (else (error "invalid format char: " char)))))

 (define format-dispatch-table 
   `((#\a . ,display)
     (#\s . ,write)
     (#\d . ,(lambda (arg port) (put-string port (number->string arg 10))))
     (#\x . ,(lambda (arg port) (put-string port (number->string arg 16))))
     (#\c . ,(lambda (arg port) (put-char port arg))))))


;; CL-style restarts to let us continue after errors.
(library (swank restarts)
    (export with-simple-restart compute-restarts invoke-restart restart-name
	    write-restart-report)
    (import (rnrs))

 (define *restarts* '())

 (define-record-type restart
   (fields name reporter continuation))
 
 (define (with-simple-restart name reporter thunk)
   (call/cc 
    (lambda (k)
      (let ((old-restarts *restarts*)
	    (restart (make-restart name (coerce-to-reporter reporter) k)))
	(dynamic-wind
	    (lambda () (set! *restarts* (cons restart old-restarts)))
	    thunk
	    (lambda () (set! *restarts* old-restarts)))))))

 (define (compute-restarts) *restarts*)

 (define (invoke-restart restart . args)
   (apply (restart-continuation restart) args))

 (define (write-restart-report restart port)
   ((restart-reporter restart) port))

 (define (coerce-to-reporter obj)
   (cond ((string? obj) (lambda (port) (put-string port obj)))
	 (#t (assert (procedure? obj)) obj)))

 )

;; This module encodes & decodes messages from the wire and queues them.
(library (swank event-queue)
    (export make-event-queue wait-for-event enqueue-event 
	    read-event write-event)
    (import (rnrs)
	    (rnrs mutable-pairs)
	    (swank format))

 (define-record-type event-queue
   (fields (mutable q) wait-fun)
   (protocol (lambda (init)
	       (lambda (wait-fun)
		 (init '() wait-fun)))))

 (define (wait-for-event q pattern)
   (or (poll q pattern)
       (begin
	 ((event-queue-wait-fun q) q)
	 (wait-for-event q pattern))))
 
 (define (poll q pattern)
   (let loop ((lag #f)
	      (l (event-queue-q q)))
     (cond ((null? l) #f)
	   ((event-match? (car l) pattern)
	    (cond (lag 
		   (set-cdr! lag (cdr l))
		   (car l))
		  (else
		   (event-queue-q-set! q (cdr l))
		   (car l))))
	   (else (loop l (cdr l))))))

 (define (event-match? event pattern)
   (cond ((or (number? pattern)
	      (member pattern '(t nil)))
	  (equal? event pattern))
	 ((symbol? pattern) #t)
	 ((pair? pattern)
	  (case (car pattern)
	    ((quote) (equal? event (cadr pattern)))
	    ((or) (exists (lambda (p) (event-match? event p)) (cdr pattern)))
	    (else (and (pair? event)
		       (event-match? (car event) (car pattern))
		       (event-match? (cdr event) (cdr pattern))))))
	 (else (error "Invalid pattern: " pattern))))
 
 (define (enqueue-event q event)
   (event-queue-q-set! q
		       (append (event-queue-q q) 
			       (list event))))

 (define (write-event event port)
   (let ((payload (call-with-string-output-port
		   (lambda (port) (write event port)))))
     (write-length (string-length payload) port)
     (put-string port payload)
     (flush-output-port port)))

 (define (write-length len port)
   (do ((i 24 (- i 4)))
       ((= i 0))
     (put-string port
		 (number->string (bitwise-bit-field len (- i 4) i)
				 16))))

 (define (read-event port)
   (let* ((header (string-append (get-string-n port 2) 
				 (get-string-n port 2)
				 (get-string-n port 2)))
	  (_ (printf "header: ~s\n" header))
	  (len (string->number header 16))
	  (_ (printf "len: ~s\n" len))
	  (payload (get-string-n port len)))
     (printf "payload: ~s\n" payload)
     (read (open-string-input-port payload))))

 )

;; Entry points for SLIME commands.
(library (swank rpc)
    (export connection-info interactive-eval
	    ;;compile-string-for-emacs 
	    throw-to-toplevel sldb-abort
	    operator-arglist buffer-first-change
	    create-repl listener-eval)
    (import (rnrs)
	    (rnrs eval)
	    (only (rnrs r5rs) scheme-report-environment)
	    (swank os)
	    (swank format)
	    (swank restarts)
	    (swank sys)
	    )
 
 (define (connection-info . _)
   `(,@'()
     :pid ,(getpid) 
     :package (:name ">" :prompt ">")
     :lisp-implementation (,@'() 
			   :name ,(implementation-name)
			   :type "R6RS-Scheme")))

 (define (interactive-eval string)
   (call-with-values 
       (lambda ()
	 (eval-in-interaction-environment (read-from-string string)))
     (case-lambda
      (() "; no value")
      ((value) (format "~s" value))
      (values (format "values: ~s" values)))))
 
 (define (throw-to-toplevel) (invoke-restart-by-name-or-nil 'toplevel))

 (define (sldb-abort) (invoke-restart-by-name-or-nil 'abort))
 
 (define (invoke-restart-by-name-or-nil name)
   (let ((r (find (lambda (r) (eq? (restart-name r) name))
		  (compute-restarts))))
     (if r (invoke-restart r) 'nil)))

 (define (create-repl target)
   (list "" ""))

 (define (listener-eval string)
   (call-with-values (lambda () (eval-region string))
     (lambda values `(:values ,@(map (lambda (v) (format "~s" v)) values)))))

 (define (eval-region string)
   (let ((sexp (read-from-string string)))
     (if (eof-object? exp)
	 (values)
	 (eval-in-interaction-environment sexp))))

 (define (read-from-string string)
   (call-with-port (open-string-input-port string) read))

 (define (operator-arglist . _) 'nil)
 (define (buffer-first-change . _) 'nil)

 )

;; The server proper.  Does the TCP stuff and exception handling.
(library (swank)
    (export start-server)
    (import (rnrs) 
	    (rnrs eval)
	    (swank os)
	    (swank format)
	    (swank event-queue)
	    (swank restarts))

 (define-record-type connection
   (fields in-port out-port event-queue))

 (define (start-server port)
   (accept-connections (or port 4005) #f))

 (define (start-server/port-file port-file)
   (accept-connections #f port-file))

 (define (accept-connections port port-file)
   (let ((sock (make-server-socket port)))
     (printf "Listening on port: ~s\n" (local-port sock))
     (when port-file 
       (write-port-file (local-port sock) port-file))
     (let-values (((in out) (accept sock (latin-1-codec))))
       (dynamic-wind 
	   (lambda () #f)
	   (lambda () 
	     (close-socket sock)
	     (serve in out))
	   (lambda () 
	     (close-port in)
	     (close-port out))))))

 (define (write-port-file port port-file)
   (call-with-output-file 
       (lambda (file) 
	 (write port file))))

 (define (serve in out) 
   (let ((err (current-error-port))
	 (q (make-event-queue 
	     (lambda (q)
	       (let ((e (read-event in)))
		 (printf "read: ~s\n" e)
		 (enqueue-event q e))))))
     (dispatch-loop (make-connection in out q))))

 (define-record-type sldb-state
   (fields level condition continuation next))

 (define (dispatch-loop conn)
   (let ((event (wait-for-event (connection-event-queue conn) 'x)))
     (case (car event)
       ((:emacs-rex) 
	(with-simple-restart 
	 'toplevel "Return to SLIME's toplevel"
	 (lambda ()
	   (apply emacs-rex conn #f (cdr event)))))
       (else (error "Unhandled event: ~s" event))))
   (dispatch-loop conn))

 (define (recover thunk on-error-thunk)
   (let ((ok #f))
     (dynamic-wind 
	 (lambda () #f) 
	 (lambda () 
	   (call-with-values thunk 
	     (lambda vals 
	       (set! ok #t) 
	       (apply values vals))))
	 (lambda ()
	   (unless ok
	     (on-error-thunk))))))

 ;; Couldn't resist to exploit the prefix feature.
 (define rpc-entries (environment '(prefix (swank rpc) swank:)))
 
 (define (emacs-rex conn sldb-state form package thread tag)
   (let ((out (connection-out-port conn)))
     (recover
      (lambda ()
	(with-exception-handler
	 (lambda (condition) 
	   (call/cc 
	    (lambda (k)
	      (sldb-exception-handler conn condition k sldb-state))))
	 (lambda ()
	   (let ((value (apply (eval (car form) rpc-entries) (cdr form))))
	     (write-event `(:return (:ok ,value) ,tag) out)))))
      (lambda ()
	(write-event `(:return (:abort) ,tag) out)))))

 (define (sldb-exception-handler connection condition k sldb-state)
   (when (serious-condition? condition)
     (let ((level (if sldb-state (+ (sldb-state-level sldb-state) 1) 1))
	   (out (connection-out-port connection)))
       (write-event `(:debug 0 ,level ,@(debugger-info condition connection))
		    out)
       (dynamic-wind
	   (lambda () #f)
	   (lambda ()
	     (sldb-loop connection 
			(make-sldb-state level condition k sldb-state)))
	   (lambda () (write-event `(:debug-return 0 ,level nil) out))))))

 (define (sldb-loop connection state)
   (apply emacs-rex connection state
	  (cdr (wait-for-event (connection-event-queue connection) 
			       '(':emacs-rex . _))))
   (sldb-loop connection state))

 (define (debugger-info condition connection)
   (list `(,(call-with-string-output-port 
	     (lambda (port) (print-condition condition port)))
	   ,(format " [type ~s]" (if (record? condition)
				     (record-type-name (record-rtd condition))
				     ))
	   ())
	 (map (lambda (r) 
		(list (format "~a" (restart-name r))
		      (call-with-string-output-port
		       (lambda (port)
			 (write-restart-report r port)))))
	      (compute-restarts))
	 '()
	 '()))

 (define (print-condition obj port)
   (cond ((condition? obj)
	  (let ((list (simple-conditions obj)))
	    (case (length list)
	      ((0)
	       (display "Compuond condition with zero components" port))
	      ((1)
	       (assert (eq? obj (car list)))
	       (print-simple-condition (car list) port))
	      (else
	       (display "Compound condition:\n" port)
	       (for-each (lambda (c)
			   (display "  " port)
			   (print-simple-condition c port)
			   (newline port))
			 list)))))
	 (#t
	  (fprintf port "Non-condition object: ~s" obj))))

 (define (print-simple-condition condition port)
   (fprintf port "~a" (record-type-name (record-rtd condition)))
   (case (count-record-fields condition)
     ((0) #f)
     ((1) 
      (fprintf port ": ")
      (do-record-fields condition (lambda (name value) (write value port))))
     (else
      (fprintf port ":")
      (do-record-fields condition (lambda (name value) 
				    (fprintf port "\n~a: ~s" name value))))))

 ;; Call FUN with RECORD's rtd and parent rtds.
 (define (do-record-rtds record fun)
   (do ((rtd (record-rtd record) (record-type-parent rtd)))
       ((not rtd))
     (fun rtd)))

 ;; Call FUN with RECORD's field names and values.
 (define (do-record-fields record fun)
   (do-record-rtds 
    record
    (lambda (rtd)
      (let* ((names (record-type-field-names rtd))
	     (len (vector-length names)))
	(do ((i 0 (+ 1 i)))
	    ((= i len))
	  (fun (vector-ref names i) ((record-accessor rtd i) record)))))))

 ;; Return the number of fields in RECORD
 (define (count-record-fields record)
   (let ((i 0))
     (do-record-rtds 
      record (lambda (rtd) 
	       (set! i (+ i (vector-length (record-type-field-names rtd))))))
     i))

 )