;;; swank-scheme48.scm --- Scheme48 backend for SLIME.
;;;
;;; Written by Helmut Eller, 2004.  
;;;
;;; Some code is copy&pasted from the Scheme48 sources.  Should work
;;; with Scheme 48 0.57.
;;;

(define-structure swank-macros (export unwind-protect)
  (open scheme)
  (begin
    (define-syntax unwind-protect 
      (syntax-rules ()
	((unwind-protect work undo ...)
	 (dynamic-wind (lambda () #f) (lambda () work)
		       (lambda () undo ...)))))
    ))

(define-structure swank (export start-swank)
  ;; Hmm... is there a "open-all-except" command?
  (open scheme swank-macros environments evaluation 
	(subset package-commands-internal (config-package))
	util sockets big-scheme fluids handle 
	conditions display-conditions 
	exceptions continuations 
	disclosers debug-data debug-data-internal debuginfo vm-exposure
	packages packages-internal
	escapes i/o posix
	list-interfaces interfaces
	primitives closures locations cells records record-types low-level
	defpackage ensures-loaded
	syntactic weak
	enumerated interrupts signals threads threads-internal)
  ;; (open menus (prefix menus::))
  (begin "
(" ;; <- force "wrong" indentation

(define nil #f)
(define t #t)
(define (1+ n) (+ n 1))
;; (define (1- n) (- n 1)) ; "1-" is invalid number syntax
(define (-1+ n) (- n 1))

(define (mappend fun list)
  (apply append (map fun list)))

(define (log-event fstring . args)
  ;;(apply format #t fstring args)
  #f
  )

(define swank-env (interaction-environment))

(define (swank port)
  (accept-connections port #f))

(define (start-swank port-file)
  (accept-connections #f port-file))

(define (accept-connections port port-file)
  (let ((socket (if port (open-socket port) (open-socket))))
    (format #t "Waiting on port ~D ~%" (socket-port-number socket))
    (if port-file (write-port-file (socket-port-number socket) port-file))
    (unwind-protect (receive (in out) (socket-accept socket) 
		      (serve-requests in out))
      (format #t "Closing socket... ~A~%" socket)
      (close-socket socket))))

(define (write-port-file portnumber filename)
  (call-with-output-file filename (lambda (p) (write portnumber p))))

;; Naming conventions for fluids is: *NAME*
(define *socket-in* (make-fluid #f))
(define *socket-out* (make-fluid #f))

;; Poor mans restarts: alist of name . continuation
(define *restarts* (make-fluid '())) 
(define call/cc call-with-current-continuation)
(define (with-restart name body)
  (call/cc (lambda (k)
	     (let-fluids *restarts* (cons (cons name k) (fluid *restarts*))
			 body))))

;;; Simple single threaded version  (interrupting doesn't work)
(define (serve-requests in out)
  (let-fluids *socket-out* out
	      *socket-in* in
	      (lambda () 
		(let loop () 
		  (with-restart 'serve-requests 
		    (lambda () (read-from-emacs)))
		  (loop)))))

(define (read-from-emacs)
  (let ((msg (read-request (fluid *socket-in*))))
    (log-event "READ: ~S~%" msg)
    (dispatch msg)))
 
(define (read-request in)
  "Read an S-expression from STREAM using the SLIME protocol."
  (let* ((len (read-length in))
	 (buffer (make-string len)))
    (fill-buffer! in buffer)
    (read-from-string buffer)))

(define (fill-buffer! in buffer)
  "Fill the string BUFFER with chars read from input port IN."
  (read-block buffer 0 (string-length buffer) in))

(define (read-length in)
  (let loop ((pos 6) (sum 0))
    (if (zero? pos) 
	sum
	(loop (-1+ pos) 
	      (+ (* sum 16) (char->hex-digit (read-char in)))))))

(define (ldb size position integer)
  "LoaD a Byte of SIZE bits at bit position POSITION from INTEGER."
  (bitwise-and (arithmetic-shift integer (- position))
	       (-1+ (arithmetic-shift 1 size))))

(define (send-to-emacs message)
  (let* ((string (prin1-to-string message))
	 (out (fluid *socket-out*)))
    (log-event "WRITE: [~D]~S~%" (string-length string) string)
    (write-length (string-length string) out)
    (write-string string out)
    (force-output out)))

(define (write-length len out)
  (do ((pos 20 (- pos 4)))
      ((< pos 0))
    (write-hex-digit (ldb 4 pos len) out)))

(define (write-hex-digit n out)
  (write-char (hex-digit->char n) out))

(define (hex-digit->char n)
  (if (< n 10)
      (ascii->char (+ (char->ascii #\0) n))
      (ascii->char (+ (char->ascii #\a) n -10))))

(define (char->hex-digit c)
  (let ((code (char->ascii c))  (a-code (char->ascii #\a)))
    (if (< code a-code)
	(- code (char->ascii #\0))
	(+ 10 (- code a-code)))))

(define (dispatch message)
  (case (car message)
    ((:emacs-rex)
     (destructure (((form package thread id) (cdr message)))
       (eval-for-emacs form package id)))))

(define (eval-for-emacs form package id)
  (with-handler debugger-hook
    (lambda ()
      (let ((ok? #f) (result #f))
	(unwind-protect
	    (begin (set! result (eval form swank-env))
		   (set! ok? #t))
	  (send-to-emacs `(:return ,(if ok? `(:ok ,result) 
					'(:abort))
				   ,id)))))))

(define *sldb-level* (make-fluid 0))
(define *sldb-restarts* (make-fluid #f))
(define *sldb-cont* (make-fluid #f))
;; (fluid *sldb-cont*)
(define (cons* car . rest)
  (if (null? rest) 
      car
      (cons car (apply cons* rest))))

(define (debugger-hook condition decline)
  (let ((level (1+ (fluid *sldb-level*))))
    (let-fluids *sldb-cont* (primitive-cwcc (lambda (k) k))
		*sldb-level* level
		*sldb-restarts* (fluid *restarts*)
		(lambda ()
		  (unwind-protect
		      (begin 
			(send-to-emacs 
			 `(:debug 0 ,level 
				  ,@(debugger-info-for-emacs condition 0 20)))
			(debug-loop level))
		    (send-to-emacs `(:debug-return 0 ,level)))))))

(define (debug-loop level)
  (with-restart (intern (format #f "return-to-sldb-level-~D" level))
    (lambda () 
      (send-to-emacs `(:debug-activate 0 ,level))
      (read-from-emacs)))
  (debug-loop level))

(define (format-restarts)
  (map (lambda (s) (list (symbol->string s) ""))
       (map car (fluid *restarts*))))

(define (debugger-info-for-emacs condition start end)
  (list (list (format-condition condition)
	      (format #f "  [~A]" (condition-type condition))
	      'nil
	      'nil)
	(format-restarts)
	(backtrace (fluid *sldb-cont*) start end)
	'()))

(define (format-condition condition)
  (trim-whitespace (with-output-to-string 
		       (lambda (p)
			 (display-condition condition p)))))

(define (swank:invoke-nth-restart-for-emacs level n)
  ((cdr (list-ref (fluid *sldb-restarts*) n)) #f))

(define (last-car list)
  (cond ((null? list) #f)
	((null? (cdr list)) (car list))
	(else (last-car (cdr list)))))

(define (swank:throw-to-toplevel)
  (let ((restart (last-car (fluid *restarts*))))
    (if restart
	((cdr restart)))))

(define (swank:connection-info) 
  (list (getpid) "Scheme 48" "scheme48" '() 'nil))

(define (getpid) (process-id->integer (get-process-id)))

(define (swank:listener-eval string) (swank:interactive-eval string))

(define (swank:interactive-eval string)
  (call-with-values
      (lambda () 
	(let ((exp (read-from-string string)))
	  (if (eof-object? exp)
	      (values)
	      (eval exp swank-env))))
    format-for-echo-area))

(define (format-for-echo-area . values)
  (if (null? values) 
      "; No value"
      (with-output-to-string
	  (lambda (out)
	    (do ((vs values (cdr vs))) ((null? vs))
	      (write (car vs) out)
	      (if (not (null? (cdr vs)))
		  (write-string ", " out)))))))

(define (swank:buffer-first-change filename) 'nil)
(define (swank:arglist-for-echo-area names) 'nil)

(define (with-output-to-string fn)
  "Call FN with a string output port and return the output as string."
  (let ((p (make-string-output-port)))
    (fn p)
    (string-output-port-output p)))

(define (read-from-string string) (read (make-string-input-port string)))

(define (prin1-to-string o) (with-output-to-string (lambda (p) (write o p))))
(define (princ-to-string o) (with-output-to-string (lambda (p) (display o p))))

(define intern string->symbol)

(define (whitespace? char) (memq (char->ascii char) ascii-whitespaces))

(define (trim-whitespace string)
  (let* ((len (string-length string))
	 (stop? (lambda (i) (not (whitespace? (string-ref string i)))))
	 (start (do ((i 0 (1+ i))) 
		    ((or (= i len) (stop? i))
		     i)))
	 (end (do ((i len (-1+ i)))
		  ((or (= i start) (stop? (-1+ i)))
		   i))))
    (substring string start end)))

;; There doesn't seem to be some central repository for packages,
;; interfaces, or structures.  Instead structures are simple stored in
;; normal variables.  Scary.
(define (%get-structure name)
  (environment-ref (config-package) name))

(define (: structure-name name)
  "Find the binding for NAME in the package named PACKAGE-NAME.
Example: (: 'pp 'p)"
  (*structure-ref (get-package package-name) name))

(define (:: structure-name name)
  (let* ((pack (structure-package (%get-structure structure-name))))
    (ensure-loaded (%get-structure structure-name))
    (location-for-reference pack name)))

(define (package package-designator)
  (let ((pd package-designator))
    (cond ((package? pd) pd)
	  ((symbol? pd) 
	   (package (environment-ref (config-package) pd)))
	  ((structure? pd)
	   (structure-package pd))
	  (t (error "No package for" pd)))))

(define (clients x)
  (population->list (cond ((package? x) (package-clients x))
			  ((structure? x) (structure-clients x))
			  ((interface? x) (interface-clients x))
			  (t (error "bug" x)))))

(define (uses x)
  (cond ((package? x) (package-opens x))
	((structure? x) (list (structure-package x)
			      (structure-interface x)))
	((interface? x) '())
	(t (error "bug" x))))

(define (module-thingy? x)
  (or (structure? x)
      (package? x)
      (interface? x)))

(define (collect-module-things package)
  (let loop ((accu '()) (worklist (list package)))
    (if (null? worklist) 
	accu
	(let ((p (car worklist)))
	  (if (memq p accu)
	      (loop accu (cdr worklist))
	      (loop (cons p accu)
		    (append (clients p)
			    (uses p)
			    (if (package? p) 
				(filter-package module-thingy? p)
				'())
			    (cdr worklist))))))))

(define (filter-package test package)
  (let ((accu '()))
    (table-walk (lambda (key val)
		  (if (not (syntax? (vector-ref val 2)))
		      (let ((v (environment-ref package key)))
			(if (test v)
			    (set! accu (cons v accu))))))
		(package-definitions package))
    accu))

(define (prefix? prefix string start)
  (let ((len (string-length prefix)))
    (if (< (string-length string) len)
	#f
	(let loop ((i 0))
	  (cond ((= i len) #t)
		((char=? (string-ref prefix i)
			 (string-ref string (+ i start)))
		 (loop (1+ i)))
		(t #f))))))

(define (match? pattern string)
  (let ((max (- (string-length string) (string-length pattern))))
    (let loop ((i 0))
      (cond ((< max i) #f)
	    ((prefix? pattern string i) #t)
	    (t (loop (1+ i)))))))

(define (apropos-list pattern)
  (let ((packages (filter package? (collect-module-things (config-package))))
	(accu '()))
    (for-each (lambda (p)
		(table-walk (lambda (key val)
			      (if (and (symbol? key)
				       (match? pattern (symbol->string key)))
				  (set! accu (cons (cons key val) accu))))
			    (package-definitions p)))
	      packages)
    accu))

(define (swank:apropos-list-for-emacs name . x)
  (map (lambda (x) (destructure (((name . def) x))
		     (list ':designator (prin1-to-string name)
			   ':variable (princ-to-string def))))
       (apropos-list name)))

(define (swank:simple-completions prefix . package) '())
  
(define (swank:backtrace from to)
  (backtrace (fluid *sldb-cont*) from to))

(define (backtrace cont from to)
  (let* ((preview (continuation-preview cont))
	 ;;(bt (remove-dynbind-frames preview))
	 (bt (safe-sublist preview from to)))
    (let rep ((i from) (bt bt))
      (if (null? bt) 
	  '()
	  (cons (list i (frame-for-emacs i (car bt)))
		(rep (1+ i) (cdr bt)))))))

(define (remove-dynbind-frames preview)
  (filter (lambda (x) (not (fluid-let-continuation-info? (car x))))
	  preview))

(define (safe-sublist list from to)
  (let outer ((l list) (i 0))
    (cond ((null? l) 
	   '())
	  ((= i from)
	   (let inner ((l l) (i i))
	     (cond ((or (<= to i) (null? l))
		    '())
		   (else 
		    (cons (car l) (inner (cdr l) (1+ i)))))))
	  (else 
	   (outer (cdr l) (1+ i))))))

(define (frame-for-emacs n frame)
  (let ((info+pc frame))
    (trim-whitespace
     (with-output-to-string 
	 (lambda (port)
	   (display-template-names (car info+pc) port))))))
  
;; ripped of from debug.scm 
;;(define (preview)
;;  (let ((cont (command-continuation)))
;;    (if cont
;;	  (display-preview (continuation-preview cont)
;;			   (command-output)))))

(define (display-preview preview port)
  (for-each (lambda (info+pc)
	      (if (not (fluid-let-continuation-info? (car info+pc)))
		  (display-template-names (car info+pc) port)))
	    preview))

(define (display-template-names info port)
  (let ((names (debug-data-names info)))
    (display "  " port)
    (if (null? names)
	(begin (display "unnamed " port)
	       (write `(id ,(if (debug-data? info)
				(debug-data-uid info)
				info))
		      port))
	(let loop ((names names))
	  (if (car names)
	      (write (car names) port)
	      (display "unnamed" port))
	  (if (and (not (null? (cdr names)))
		   (cadr names))
	      (begin (display " in " port)
		     (loop (cdr names))))))
    (newline port)))

(define fluid-let-continuation-info?	;Incestuous!
  (let ((id (let-fluid (make-fluid #f) #f
	      (lambda ()
		(primitive-catch (lambda (k)
				   (template-id
				    (continuation-template k))))))))
    (lambda (info) 
      (eqv? (if (debug-data? info)
		(debug-data-uid info)
		info)
	    id))))

(define (nth-frame n)
  (do ((c (fluid *sldb-cont*) (continuation-parent c))
       (n n (- n 1)))
      ((zero? n) c)))

(define (swank:frame-locals-for-emacs n)
  (map (lambda (x) (destructure (((label value) x))
		     (list ':name (princ-to-string label)
			   ':value (prin1-to-string value)
			   ':id 0)))
       (prepare-continuation-menu (nth-frame n))))

(define (swank:frame-catch-tags-for-emacs . x)
  'nil)

(define (continuation-debug-data thing)
  (template-debug-data (continuation-template thing)))

;;; Inspector

(define inspectee #f)
(define inspectee-parts #f)
(define inspector-stack '())
(define inspector-history '())
(define inspector-history-index 0)
(define inspect-length 30)

(define (reset-inspector)
  (set! inspectee #f)
  (set! inspectee-parts '())
  (set! inspector-stack '())
  (set! inspector-history '()))

(define (swank:init-inspector string)
   (reset-inspector)
   (inspect-object (eval (read-from-string string) swank-env)))

(define (inspector-nth-part index)
  (list-ref inspectee-parts index))
 
(define (swank:inspect-nth-part index)
  (inspect-object (inspector-nth-part index)))
 
(define (swank:inspector-pop)
  (cond ((pair? (cdr inspector-stack))
	 (let ((o (cadr inspector-stack)))
	   (set! inspector-stack (cddr inspector-stack))
	   (inspect-object o)))
	(t 'nil)))
 
(define (swank:inspector-next)
  (let ((pos (position inspectee inspector-history)))
    (cond ((= pos (- (length inspector-history) 1))
	   'nil)
	  (t (inspect-object (list-ref inspector-history (1+ pos)))))))

(define (swank:quit-inspector)
  (reset-inspector)
  'nil)

(define (print-part-to-string value)
  (let ((string (prin1-to-string value))
	(pos (position value inspector-history)))
    (if pos
	(format #f "#~D=~A" pos string)
	string)))

(define (inspect-object o)
  (set! inspectee o)
  (set! inspector-stack (cons o inspector-stack))
  (let ((pos (position o inspector-history)))
    (if (not pos)
	(set! inspector-history (append inspector-history (list o)))))
  (receive (text type parts) (inspected-parts o)
    (set! inspectee-parts parts)
    (list ':title (format #f "~A ~A" (print-part-to-string o) text)
	  ':type type
	  ':content (mappend contents-for-part parts))))

(define (contents-for-part part)
  (destructure (((label value) part))
    `(,(princ-to-string label)
      ": " (:value ,(print-part-to-string value) ,(assign-index value))
      ,(string #\newline))))

(define (assign-index value)
  (or (position value inspectee-parts)
      (let ((len (length inspectee-parts)))
	(set! inspectee-parts (append inspectee-parts (list value)))
	len)))
  
(define (indexed-parts thing length ref)
  (do ((i (- length 1)  (- i 1))
       (a '() (cons (list i (ref thing i)) a)))
      ((= -1 i) a)))

;; A lot of stuff below is copied from menu.scm.

(define (inspected-parts thing)
  (cond ((vector? thing)
	 (values "is a vector" "vector"
		 (indexed-parts thing (vector-length thing) vector-ref)))
	((template? thing)
	 (values "is a template" "template"
		 (indexed-parts thing (template-length thing) template-ref)))
	((pair? thing)
	 (let ((length (careful-length thing)))
	   (if (eq? length 'improper)
	       (values "is a pair (or improper list)" "cons"
		       `((car ,(car thing))
			 (cdr ,(cdr thing))))
	       (values "is a list" "cons"
		       (indexed-parts thing length list-ref)))))
	((closure? thing)
	 (values "is a closure" "closure" 
		 (prepare-environment-menu
		  (closure-env thing)
		  (debug-data-env-shape (template-debug-data
					 (closure-template thing))
					0))))
	((continuation? thing)
	 (values "is a closure" "closure" 
		 (prepare-continuation-menu thing)))
	((record? thing)
	 (values "is a record" "record" 
		 (prepare-record-menu thing)))
	((location? thing)
	 (values "is a location" "location" 
		 `((id ,(location-id thing))
		   (contents ,(contents thing)))))
	((cell? thing)
	 (values "is a cell" "cell" 
		 `((ref ,(cell-ref thing)))))
	((weak-pointer? thing)
	 (values "weak-pointer" "weak-pointer" 
		 `((ref ,(weak-pointer-ref thing)))))
	(t (values "dunno" "???" `()))))

(define (careful-length list)
  (let loop ((fast list) (len 0) (slow list) (move-slow? #f))
    (cond ((eq? '() fast)
	   len)
	  ((not (pair? fast))
	   'improper)
	  ((not move-slow?)
	   (loop (cdr fast) (+ len 1) slow #t))
	  ((eq? fast slow)
	   'circular)
	  (else
	   (loop (cdr fast) (+ len 1) (cdr slow) #f)))))

(define (prepare-environment-menu env shape)
  (if (vector? env)
      (let ((values (rib-values env)))
        (if (pair? shape)
            (append (map list (car shape) values)
                    (prepare-environment-menu (vector-ref env 0)
                                              (cdr shape)))
            (append (map (lambda (x)
			   (list #f x))
			 values)
                    (prepare-environment-menu (vector-ref env 0)
					      shape))))
      '()))

(define (rib-values env)
  (let ((z (vector-length env)))
    (do ((i 1 (+ i 1))
	 (l '() (cons (if (vector-unassigned? env i)
			  'unassigned
			  (vector-ref env i))
		      l)))
	((>= i z) l))))

(define (prepare-record-menu thing)
  (let ((rt (record-type thing))
        (z (record-length thing)))
    (if (record-type? rt)
        (do ((i (- z 1) (- i 1))
             (f (reverse (record-type-field-names rt)) (cdr f))
             (l '() (cons (list (car f) (record-ref thing i)) l)))
            ((< i 1) l))
        (do ((i (- z 1) (- i 1))
             (l '() (cons (list #f (record-ref thing i)) l)))
            ((< i 0) l)))))

(define (prepare-continuation-menu thing)
  (let ((next (continuation-parent thing)))
    `(,@(let recur ((c thing))
	  (if (eq? c next)
	      '()
	      (let ((z (continuation-arg-count c)))
		(do ((i (- z 1) (- i 1))
		     (l (recur (continuation-cont c))
			(cons (list #f (continuation-arg c i))
			      l)))
		    ((< i 0) l)))))
      ,@(prepare-environment-menu
	  (continuation-env thing)
	  (debug-data-env-shape (continuation-debug-data thing)
				(continuation-pc thing))))))))

;; Local Variables:
;; eval: (put 'unwind-protect 'scheme-indent-function 1)
;; eval: (put 'receive 'scheme-indent-function 2)
;; eval: (put 'destructure 'scheme-indent-function 1)
;; End:
