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