|
|
- ;; 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))
-
- )
|