diff options
Diffstat (limited to 'vim/bundle/slimv/slime/contrib/swank-kawa.scm')
-rw-r--r-- | vim/bundle/slimv/slime/contrib/swank-kawa.scm | 2498 |
1 files changed, 2498 insertions, 0 deletions
diff --git a/vim/bundle/slimv/slime/contrib/swank-kawa.scm b/vim/bundle/slimv/slime/contrib/swank-kawa.scm new file mode 100644 index 0000000..843037b --- /dev/null +++ b/vim/bundle/slimv/slime/contrib/swank-kawa.scm @@ -0,0 +1,2498 @@ +;;;; swank-kawa.scm --- Swank server for Kawa +;;; +;;; Copyright (C) 2007 Helmut Eller +;;; +;;; This file is licensed under the terms of the GNU General Public +;;; License as distributed with Emacs (press C-h C-c for details). + +;;;; Installation +;; +;; 1. You need Kawa (version 2.x) and a JVM with debugger support. +;; +;; 2. Compile this file and create swank-kawa.jar with: +;; java -cp kawa.jar:$JAVA_HOME/lib/tools.jar \ +;; -Xss2M kawa.repl --r7rs -d classes -C swank-kawa.scm && +;; jar cf swank-kawa.jar -C classes . +;; +;; 3. Add something like this to your .emacs: +#| +;; Kawa, Swank, and the debugger classes (tools.jar) must be in the +;; classpath. You also need to start the debug agent. +(setq slime-lisp-implementations + '((kawa + ("java" + ;; needed jar files + "-cp" "kawa-2.0.1.jar:swank-kawa.jar:/opt/jdk1.8.0/lib/tools.jar" + ;; channel for debugger + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n" + ;; depending on JVM, compiler may need more stack + "-Xss2M" + ;; kawa without GUI + "kawa.repl" "-s") + :init kawa-slime-init))) + +(defun kawa-slime-init (file _) + (setq slime-protocol-version 'ignore) + (format "%S\n" + `(begin (import (swank-kawa)) + (start-swank ,file) + ;; Optionally add source paths of your code so + ;; that M-. works better: + ;;(set! swank-java-source-path + ;; (append + ;; '(,(expand-file-name "~/lisp/slime/contrib/") + ;; "/scratch/kawa") + ;; swank-java-source-path)) + ))) + +;; Optionally define a command to start it. +(defun kawa () + (interactive) + (slime 'kawa)) + +|# +;; 4. Start everything with M-- M-x slime kawa +;; +;; + + +;;; Code: + +(define-library (swank macros) + (export df fun seq set fin esc + ! !! !s @ @s + when unless while dotimes dolist for packing with pushf == assert + mif mcase mlet mlet* typecase ignore-errors + ferror + ) + (import (scheme base) + (only (kawa base) + syntax + quasisyntax + syntax-case + define-syntax-case + identifier? + + invoke + invoke-static + field + static-field + instance? + try-finally + try-catch + primitive-throw + + format + reverse! + as + )) + (begin " +(" + +(define (ferror fstring #!rest args) + (let ((err (<java.lang.Error> + (as <java.lang.String> (apply format fstring args))))) + (primitive-throw err))) + +(define (rewrite-lambda-list args) + (syntax-case args () + (() #`()) + ((rest x ...) (eq? #'rest #!rest) args) + ((optional x ...) (eq? #'optional #!optional) args) + ((var args ...) (identifier? #'var) + #`(var #,@(rewrite-lambda-list #'(args ...)))) + (((var type) args ...) (identifier? #'var) + #`((var :: type) #,@(rewrite-lambda-list #'(args ...)))))) + +(define-syntax df + (lambda (stx) + (syntax-case stx (=>) + ((df name (args ... => return-type) body ...) + #`(define (name #,@(rewrite-lambda-list #'(args ...))) :: return-type + (seq body ...))) + ((df name (args ...) body ...) + #`(define (name #,@(rewrite-lambda-list #'(args ...))) + (seq body ...)))))) + +(define-syntax fun + (lambda (stx) + (syntax-case stx (=>) + ((fun (args ... => return-type) body ...) + #`(lambda #,(rewrite-lambda-list #'(args ...)) :: return-type + (seq body ...))) + ((fun (args ...) body ...) + #`(lambda #,(rewrite-lambda-list #'(args ...)) + (seq body ...)))))) + +(define-syntax fin + (syntax-rules () + ((fin body handler ...) + (try-finally body (seq handler ...))))) + +(define-syntax seq + (syntax-rules () + ((seq) + (begin #!void)) + ((seq body ...) + (begin body ...)))) + +(define-syntax esc + (syntax-rules () + ((esc abort body ...) + (let* ((key (<symbol>)) + (abort (lambda (val) (throw key val)))) + (catch key + (lambda () body ...) + (lambda (key val) val)))))) + +(define-syntax ! + (syntax-rules () + ((! name obj args ...) + (invoke obj 'name args ...)))) + +(define-syntax !! + (syntax-rules () + ((!! name1 name2 obj args ...) + (! name1 (! name2 obj args ...))))) + +(define-syntax !s + (syntax-rules () + ((! class name args ...) + (invoke-static class 'name args ...)))) + +(define-syntax @ + (syntax-rules () + ((@ name obj) + (field obj 'name)))) + +(define-syntax @s + (syntax-rules (quote) + ((@s class name) + (static-field class (quote name))))) + +(define-syntax while + (syntax-rules () + ((while exp body ...) + (do () ((not exp)) body ...)))) + +(define-syntax dotimes + (syntax-rules () + ((dotimes (i n result) body ...) + (let ((max :: <int> n)) + (do ((i :: <int> 0 (as <int> (+ i 1)))) + ((= i max) result) + body ...))) + ((dotimes (i n) body ...) + (dotimes (i n #f) body ...)))) + +(define-syntax dolist + (syntax-rules () + ((dolist (e list) body ... ) + (for ((e list)) body ...)))) + +(define-syntax for + (syntax-rules () + ((for ((var iterable)) body ...) + (let ((iter (! iterator iterable))) + (while (! has-next iter) + ((lambda (var) body ...) + (! next iter))))))) + +(define-syntax packing + (syntax-rules () + ((packing (var) body ...) + (let ((var :: <list> '())) + (let ((var (lambda (v) (set! var (cons v var))))) + body ...) + (reverse! var))))) + +;;(define-syntax loop +;; (syntax-rules (for = then collect until) +;; ((loop for var = init then step until test collect exp) +;; (packing (pack) +;; (do ((var init step)) +;; (test) +;; (pack exp)))) +;; ((loop while test collect exp) +;; (packing (pack) (while test (pack exp)))))) + +(define-syntax with + (syntax-rules () + ((with (vars ... (f args ...)) body ...) + (f args ... (lambda (vars ...) body ...))))) + +(define-syntax pushf + (syntax-rules () + ((pushf value var) + (set! var (cons value var))))) + +(define-syntax == + (syntax-rules () + ((== x y) + (eq? x y)))) + +(define-syntax set + (syntax-rules () + ((set x y) + (let ((tmp y)) + (set! x tmp) + tmp)) + ((set x y more ...) + (begin (set! x y) (set more ...))))) + +(define-syntax assert + (syntax-rules () + ((assert test) + (seq + (when (not test) + (error "Assertion failed" 'test)) + 'ok)) + ((assert test fstring args ...) + (seq + (when (not test) + (error "Assertion failed" 'test (format #f fstring args ...))) + 'ok)))) + +(define-syntax mif + (syntax-rules (quote unquote _) + ((mif ('x value) then else) + (if (equal? 'x value) then else)) + ((mif (,x value) then else) + (if (eq? x value) then else)) + ((mif (() value) then else) + (if (eq? value '()) then else)) + #| This variant produces no lambdas but breaks the compiler + ((mif ((p . ps) value) then else) + (let ((tmp value) + (fail? :: <int> 0) + (result #!null)) + (if (instance? tmp <pair>) + (let ((tmp :: <pair> tmp)) + (mif (p (! get-car tmp)) + (mif (ps (! get-cdr tmp)) + (set! result then) + (set! fail? -1)) + (set! fail? -1))) + (set! fail? -1)) + (if (= fail? 0) result else))) + |# + ((mif ((p . ps) value) then else) + (let ((fail (lambda () else)) + (tmp value)) + (if (instance? tmp <pair>) + (let ((tmp :: <pair> tmp)) + (mif (p (! get-car tmp)) + (mif (ps (! get-cdr tmp)) + then + (fail)) + (fail))) + (fail)))) + ((mif (_ value) then else) + then) + ((mif (var value) then else) + (let ((var value)) then)) + ((mif (pattern value) then) + (mif (pattern value) then (values))))) + +(define-syntax mcase + (syntax-rules () + ((mcase exp (pattern body ...) more ...) + (let ((tmp exp)) + (mif (pattern tmp) + (begin body ...) + (mcase tmp more ...)))) + ((mcase exp) (ferror "mcase failed ~s\n~a" 'exp exp)))) + +(define-syntax mlet + (syntax-rules () + ((mlet (pattern value) body ...) + (let ((tmp value)) + (mif (pattern tmp) + (begin body ...) + (error "mlet failed" tmp)))))) + +(define-syntax mlet* + (syntax-rules () + ((mlet* () body ...) (begin body ...)) + ((mlet* ((pattern value) ms ...) body ...) + (mlet (pattern value) (mlet* (ms ...) body ...))))) + +(define-syntax typecase% + (syntax-rules (eql or satisfies) + ((typecase% var (#t body ...) more ...) + (seq body ...)) + ((typecase% var ((eql value) body ...) more ...) + (cond ((eqv? var 'value) body ...) + (else (typecase% var more ...)))) + ((typecase% var ((satisfies predicate) body ...) more ...) + (cond ((predicate var) body ...) + (else (typecase% var more ...)))) + ((typecase% var ((or type) body ...) more ...) + (typecase% var (type body ...) more ...)) + ((typecase% var ((or type ...) body ...) more ...) + (let ((f (lambda (var) body ...))) + (typecase% var + (type (f var)) ... + (#t (typecase% var more ...))))) + ((typecase% var (type body ...) more ...) + (cond ((instance? var type) + (let ((var :: type (as type var))) + body ...)) + (else (typecase% var more ...)))) + ((typecase% var) + (error "typecase% failed" var + (! getClass (as <object> var)))))) + +(define-syntax typecase + (lambda (stx) + (syntax-case stx () + ((_ exp more ...) (identifier? (syntax exp)) + #`(typecase% exp more ...)) + ((_ exp more ...) + #`(let ((tmp exp)) + (typecase% tmp more ...)))))) + +(define-syntax ignore-errors + (syntax-rules () + ((ignore-errors body ...) + (try-catch (seq body ...) + (v <java.lang.Error> #f) + (v <java.lang.Exception> #f))))) + +)) + +(define-library (swank-kawa) + (export start-swank + create-swank-server + swank-java-source-path + break) + (import (scheme base) + (scheme file) + (scheme repl) + (scheme read) + (scheme write) + (scheme eval) + (scheme process-context) + (swank macros) + (only (kawa base) + + define-alias + define-variable + + define-simple-class + this + + invoke-special + instance? + as + + primitive-throw + try-finally + try-catch + synchronized + + call-with-input-string + call-with-output-string + force-output + format + + make-process + command-parse + + runnable + + scheme-implementation-version + reverse! + ) + (rnrs hashtables) + (only (gnu kawa slib syntaxutils) expand) + (only (kawa regex) regex-match)) + (begin " +(" + + +;;(define-syntax dc +;; (syntax-rules () +;; ((dc name () %% (props ...) prop more ...) +;; (dc name () %% (props ... (prop <object>)) more ...)) +;; ;;((dc name () %% (props ...) (prop type) more ...) +;; ;; (dc name () %% (props ... (prop type)) more ...)) +;; ((dc name () %% ((prop type) ...)) +;; (define-simple-class name () +;; ((*init* (prop :: type) ...) +;; (set (field (this) 'prop) prop) ...) +;; (prop :type type) ...)) +;; ((dc name () props ...) +;; (dc name () %% () props ...)))) + + +;;;; Aliases + +(define-alias <server-socket> java.net.ServerSocket) +(define-alias <socket> java.net.Socket) +(define-alias <in> java.io.InputStreamReader) +(define-alias <out> java.io.OutputStreamWriter) +(define-alias <in-port> gnu.kawa.io.InPort) +(define-alias <out-port> gnu.kawa.io.OutPort) +(define-alias <file> java.io.File) +(define-alias <str> java.lang.String) +(define-alias <builder> java.lang.StringBuilder) +(define-alias <throwable> java.lang.Throwable) +(define-alias <source-error> gnu.text.SourceError) +(define-alias <module-info> gnu.expr.ModuleInfo) +(define-alias <iterable> java.lang.Iterable) +(define-alias <thread> java.lang.Thread) +(define-alias <queue> java.util.concurrent.LinkedBlockingQueue) +(define-alias <exchanger> java.util.concurrent.Exchanger) +(define-alias <timeunit> java.util.concurrent.TimeUnit) +(define-alias <vm> com.sun.jdi.VirtualMachine) +(define-alias <mirror> com.sun.jdi.Mirror) +(define-alias <value> com.sun.jdi.Value) +(define-alias <thread-ref> com.sun.jdi.ThreadReference) +(define-alias <obj-ref> com.sun.jdi.ObjectReference) +(define-alias <array-ref> com.sun.jdi.ArrayReference) +(define-alias <str-ref> com.sun.jdi.StringReference) +(define-alias <meth-ref> com.sun.jdi.Method) +(define-alias <class-type> com.sun.jdi.ClassType) +(define-alias <ref-type> com.sun.jdi.ReferenceType) +(define-alias <frame> com.sun.jdi.StackFrame) +(define-alias <field> com.sun.jdi.Field) +(define-alias <local-var> com.sun.jdi.LocalVariable) +(define-alias <location> com.sun.jdi.Location) +(define-alias <absent-exc> com.sun.jdi.AbsentInformationException) +(define-alias <event> com.sun.jdi.event.Event) +(define-alias <exception-event> com.sun.jdi.event.ExceptionEvent) +(define-alias <step-event> com.sun.jdi.event.StepEvent) +(define-alias <breakpoint-event> com.sun.jdi.event.BreakpointEvent) +(define-alias <env> gnu.mapping.Environment) + +(define-simple-class <chan> () + (owner :: <thread> #:init (!s java.lang.Thread currentThread)) + (peer :: <chan>) + (queue :: <queue> #:init (<queue>)) + (lock #:init (<object>))) + + +;;;; Entry Points + +(df create-swank-server (port-number) + (setup-server port-number announce-port)) + +(df start-swank (port-file) + (let ((announce (fun ((socket <server-socket>)) + (with (f (call-with-output-file port-file)) + (format f "~d\n" (! get-local-port socket)))))) + (spawn (fun () + (setup-server 0 announce))))) + +(df setup-server ((port-number <int>) announce) + (! set-name (current-thread) "swank") + (let ((s (<server-socket> port-number))) + (announce s) + (let ((c (! accept s))) + (! close s) + (log "connection: ~s\n" c) + (fin (dispatch-events c) + (log "closing socket: ~a\n" s) + (! close c))))) + +(df announce-port ((socket <server-socket>)) + (log "Listening on port: ~d\n" (! get-local-port socket))) + + +;;;; Event dispatcher + +(define-variable *the-vm* #f) +(define-variable *last-exception* #f) +(define-variable *last-stacktrace* #f) +(df %vm (=> <vm>) *the-vm*) + +;; FIXME: this needs factorization. But I guess the whole idea of +;; using bidirectional channels just sucks. Mailboxes owned by a +;; single thread to which everybody can send are much easier to use. + +(df dispatch-events ((s <socket>)) + (mlet* ((charset "iso-8859-1") + (ins (<in> (! getInputStream s) charset)) + (outs (<out> (! getOutputStream s) charset)) + ((in . _) (spawn/chan/catch (fun (c) (reader ins c)))) + ((out . _) (spawn/chan/catch (fun (c) (writer outs c)))) + ((dbg . _) (spawn/chan/catch vm-monitor)) + (user-env (interaction-environment)) + (x (seq + (! set-flag user-env #t #|<env>:THREAD_SAFE|# 8) + (! set-flag user-env #f #|<env>:DIRECT_INHERITED_ON_SET|# 16) + #f)) + ((listener . _) + (spawn/chan (fun (c) (listener c user-env)))) + (inspector #f) + (threads '()) + (repl-thread #f) + (extra '()) + (vm (let ((vm #f)) (fun () (or vm (rpc dbg `(get-vm))))))) + (while #t + (mlet ((c . event) (recv* (append (list in out dbg listener) + (if inspector (list inspector) '()) + (map car threads) + extra))) + ;;(log "event: ~s\n" event) + (mcase (list c event) + ((_ (':emacs-rex ('|swank:debugger-info-for-emacs| from to) + pkg thread id)) + (send dbg `(debug-info ,thread ,from ,to ,id))) + ((_ (':emacs-rex ('|swank:throw-to-toplevel|) pkg thread id)) + (send dbg `(throw-to-toplevel ,thread ,id))) + ((_ (':emacs-rex ('|swank:sldb-continue|) pkg thread id)) + (send dbg `(thread-continue ,thread ,id))) + ((_ (':emacs-rex ('|swank:frame-source-location| frame) + pkg thread id)) + (send dbg `(frame-src-loc ,thread ,frame ,id))) + ((_ (':emacs-rex ('|swank:frame-locals-and-catch-tags| frame) + pkg thread id)) + (send dbg `(frame-details ,thread ,frame ,id))) + ((_ (':emacs-rex ('|swank:sldb-disassemble| frame) + pkg thread id)) + (send dbg `(disassemble-frame ,thread ,frame ,id))) + ((_ (':emacs-rex ('|swank:backtrace| from to) pkg thread id)) + (send dbg `(thread-frames ,thread ,from ,to ,id))) + ((_ (':emacs-rex ('|swank:list-threads|) pkg thread id)) + (send dbg `(list-threads ,id))) + ((_ (':emacs-rex ('|swank:debug-nth-thread| n) _ _ _)) + (send dbg `(debug-nth-thread ,n))) + ((_ (':emacs-rex ('|swank:quit-thread-browser|) _ _ id)) + (send dbg `(quit-thread-browser ,id))) + ((_ (':emacs-rex ('|swank:init-inspector| str . _) pkg _ id)) + (set inspector (make-inspector user-env (vm))) + (send inspector `(init ,str ,id))) + ((_ (':emacs-rex ('|swank:inspect-frame-var| frame var) + pkg thread id)) + (mlet ((im . ex) (chan)) + (set inspector (make-inspector user-env (vm))) + (send dbg `(get-local ,ex ,thread ,frame ,var)) + (send inspector `(init-mirror ,im ,id)))) + ((_ (':emacs-rex ('|swank:inspect-current-condition|) pkg thread id)) + (mlet ((im . ex) (chan)) + (set inspector (make-inspector user-env (vm))) + (send dbg `(get-exception ,ex ,thread)) + (send inspector `(init-mirror ,im ,id)))) + ((_ (':emacs-rex ('|swank:inspect-nth-part| n) pkg _ id)) + (send inspector `(inspect-part ,n ,id))) + ((_ (':emacs-rex ('|swank:inspector-pop|) pkg _ id)) + (send inspector `(pop ,id))) + ((_ (':emacs-rex ('|swank:quit-inspector|) pkg _ id)) + (send inspector `(quit ,id))) + ((_ (':emacs-interrupt id)) + (let* ((vm (vm)) + (t (find-thread id (map cdr threads) repl-thread vm))) + (send dbg `(interrupt-thread ,t)))) + ((_ (':emacs-rex form _ _ id)) + (send listener `(,form ,id))) + ((_ ('get-vm c)) + (send dbg `(get-vm ,c))) + ((_ ('get-channel c)) + (mlet ((im . ex) (chan)) + (pushf im extra) + (send c ex))) + ((_ ('forward x)) + (send out x)) + ((_ ('set-listener x)) + (set repl-thread x)) + ((_ ('publish-vm vm)) + (set *the-vm* vm)) + ))))) + +(df find-thread (id threads listener (vm <vm>)) + (cond ((== id ':repl-thread) listener) + ((== id 't) listener + ;;(if (null? threads) + ;; listener + ;; (vm-mirror vm (car threads))) + ) + (#t + (let ((f (find-if threads + (fun (t :: <thread>) + (= id (! uniqueID + (as <thread-ref> (vm-mirror vm t))))) + #f))) + (cond (f (vm-mirror vm f)) + (#t listener)))))) + + +;;;; Reader thread + +(df reader ((in <in>) (c <chan>)) + (! set-name (current-thread) "swank-net-reader") + (let ((rt (!s gnu.kawa.lispexpr.ReadTable createInitial))) ; ':' not special + (while #t + (send c (decode-message in rt))))) + +(df decode-message ((in <in>) (rt <gnu.kawa.lispexpr.ReadTable>) => <list>) + (let* ((header (read-chunk in 6)) + (len (!s java.lang.Integer parseInt header 16))) + (call-with-input-string (read-chunk in len) + (fun ((port <input-port>)) + (%read port rt))))) + +(df read-chunk ((in <in>) (len <int>) => <str>) + (let ((chars (<char[]> #:length len))) + (let loop ((offset :: <int> 0)) + (cond ((= offset len) (<str> chars)) + (#t (let ((count (! read in chars offset (- len offset)))) + (assert (not (= count -1)) "partial packet") + (loop (+ offset count)))))))) + +;;; FIXME: not thread safe +(df %read ((port <in-port>) (table <gnu.kawa.lispexpr.ReadTable>)) + (let ((old (!s gnu.kawa.lispexpr.ReadTable getCurrent))) + (try-finally + (seq (!s gnu.kawa.lispexpr.ReadTable setCurrent table) + (read port)) + (!s gnu.kawa.lispexpr.ReadTable setCurrent old)))) + + +;;;; Writer thread + +(df writer ((out <out>) (c <chan>)) + (! set-name (current-thread) "swank-net-writer") + (while #t + (encode-message out (recv c)))) + +(df encode-message ((out <out>) (message <list>)) + (let ((builder (<builder> (as <int> 512)))) + (print-for-emacs message builder) + (! write out (! toString (format "~6,'0x" (! length builder)))) + (! write out builder) + (! flush out))) + +(df print-for-emacs (obj (out <builder>)) + (let ((pr (fun (o) (! append out (! toString (format "~s" o))))) + (++ (fun ((s <string>)) (! append out (! toString s))))) + (cond ((null? obj) (++ "nil")) + ((string? obj) (pr obj)) + ((number? obj) (pr obj)) + ;;((keyword? obj) (++ ":") (! append out (to-str obj))) + ((symbol? obj) (pr obj)) + ((pair? obj) + (++ "(") + (let loop ((obj obj)) + (print-for-emacs (car obj) out) + (let ((cdr (cdr obj))) + (cond ((null? cdr) (++ ")")) + ((pair? cdr) (++ " ") (loop cdr)) + (#t (++ " . ") (print-for-emacs cdr out) (++ ")")))))) + (#t (error "Unprintable object" obj))))) + +;;;; SLIME-EVAL + +(df eval-for-emacs ((form <list>) env (id <int>) (c <chan>)) + ;;(! set-uncaught-exception-handler (current-thread) + ;; (<ucex-handler> (fun (t e) (reply-abort c id)))) + (reply c (%eval form env) id)) + +(define-variable *slime-funs*) +(set *slime-funs* (tab)) + +(df %eval (form env) + (apply (lookup-slimefun (car form) *slime-funs*) env (cdr form))) + +(df lookup-slimefun ((name <symbol>) tab) + ;; name looks like '|swank:connection-info| + (or (get tab name #f) + (ferror "~a not implemented" name))) + +(df %defslimefun ((name <symbol>) (fun <procedure>)) + (let ((string (symbol->string name))) + (cond ((regex-match #/:/ string) + (put *slime-funs* name fun)) + (#t + (let ((qname (string->symbol (string-append "swank:" string)))) + (put *slime-funs* qname fun)))))) + +(define-syntax defslimefun + (syntax-rules () + ((defslimefun name (args ...) body ...) + (seq + (df name (args ...) body ...) + (%defslimefun 'name name))))) + +(defslimefun connection-info ((env <env>)) + (let ((prop (fun (name) (!s java.lang.System getProperty name)))) + `(:pid + 0 + :style :spawn + :lisp-implementation (:type "Kawa" :name "kawa" + :version ,(scheme-implementation-version)) + :machine (:instance ,(prop "java.vm.name") :type ,(prop "os.name") + :version ,(prop "java.runtime.version")) + :features () + :package (:name "??" :prompt ,(! getName env)) + :encoding (:coding-systems ("iso-8859-1")) + ))) + + +;;;; Listener + +(df listener ((c <chan>) (env <env>)) + (! set-name (current-thread) "swank-listener") + (log "listener: ~s ~s ~s ~s\n" + (current-thread) (! hashCode (current-thread)) c env) + (let ((out (make-swank-outport (rpc c `(get-channel))))) + (set (current-output-port) out) + (let ((vm (as <vm> (rpc c `(get-vm))))) + (send c `(set-listener ,(vm-mirror vm (current-thread)))) + (request-uncaught-exception-events vm) + ;;stack snaphost are too expensive + ;;(request-caught-exception-events vm) + ) + (rpc c `(get-vm)) + (listener-loop c env out))) + +(define-simple-class <listener-abort> (<throwable>) + ((*init*) + (invoke-special <throwable> (this) '*init* )) + ((abort) :: void + (primitive-throw (this)))) + +(df listener-loop ((c <chan>) (env <env>) port) + (while (not (nul? c)) + ;;(log "listener-loop: ~s ~s\n" (current-thread) c) + (mlet ((form id) (recv c)) + (let ((restart (fun () + (close-port port) + (reply-abort c id) + (send (car (spawn/chan + (fun (cc) + (listener (recv cc) env)))) + c) + (set c #!null)))) + (! set-uncaught-exception-handler (current-thread) + (<ucex-handler> (fun (t e) (restart)))) + (try-catch + (let* ((val (%eval form env))) + (force-output) + (reply c val id)) + (ex <java.lang.Exception> (invoke-debugger ex) (restart)) + (ex <java.lang.Error> (invoke-debugger ex) (restart)) + (ex <listener-abort> + (let ((flag (!s java.lang.Thread interrupted))) + (log "listener-abort: ~s ~a\n" ex flag)) + (restart)) + ))))) + +(df invoke-debugger (condition) + ;;(log "should now invoke debugger: ~a" condition) + (try-catch + (break condition) + (ex <listener-abort> (seq)))) + +(defslimefun |swank-repl:create-repl| (env #!rest _) + (list "user" "user")) + +(defslimefun interactive-eval (env str) + (values-for-echo-area (eval (read-from-string str) env))) + +(defslimefun interactive-eval-region (env (s <string>)) + (with (port (call-with-input-string s)) + (values-for-echo-area + (let next ((result (values))) + (let ((form (read port))) + (cond ((== form #!eof) result) + (#t (next (eval form env))))))))) + +(defslimefun |swank-repl:listener-eval| (env string) + (let* ((form (read-from-string string)) + (list (values-to-list (eval form env)))) + `(:values ,@(map pprint-to-string list)))) + +(defslimefun pprint-eval (env string) + (let* ((form (read-from-string string)) + (l (values-to-list (eval form env)))) + (apply cat (map pprint-to-string l)))) + +(df call-with-abort (f) + (try-catch (f) (ex <throwable> (exception-message ex)))) + +(df exception-message ((ex <throwable>)) + (typecase ex + (<kawa.lang.NamedException> (! to-string ex)) + (<throwable> (format "~a: ~a" + (class-name-sans-package ex) + (! getMessage ex))))) + +(df values-for-echo-area (values) + (let ((values (values-to-list values))) + (cond ((null? values) "; No value") + (#t (format "~{~a~^, ~}" (map pprint-to-string values)))))) + +;;;; Compilation + +(defslimefun compile-file-for-emacs (env (filename <str>) load? + #!optional options) + (let ((jar (cat (path-sans-extension (filepath filename)) ".jar"))) + (wrap-compilation + (fun ((m <gnu.text.SourceMessages>)) + (!s kawa.lang.CompileFile read filename m)) + jar (if (lisp-bool load?) env #f) #f))) + +(df wrap-compilation (f jar env delete?) + (let ((start-time (current-time)) + (messages (<gnu.text.SourceMessages>))) + (try-catch + (let ((c (as <gnu.expr.Compilation> (f messages)))) + (set (@ explicit c) #t) + (! compile-to-archive c (! get-module c) jar)) + (ex <throwable> + (log "error during compilation: ~a\n~a" ex (! getStackTrace ex)) + (! error messages (as <char> #\f) + (to-str (exception-message ex)) #!null) + #f)) + (log "compilation done.\n") + (let ((success? (zero? (! get-error-count messages)))) + (when (and env success?) + (log "loading ...\n") + (eval `(load ,jar) env) + (log "loading ... done.\n")) + (when delete? + (ignore-errors (delete-file jar) #f)) + (let ((end-time (current-time))) + (list ':compilation-result + (compiler-notes-for-emacs messages) + (if success? 't 'nil) + (/ (- end-time start-time) 1000.0)))))) + +(defslimefun compile-string-for-emacs (env string buffer offset dir) + (wrap-compilation + (fun ((m <gnu.text.SourceMessages>)) + (let ((c (as <gnu.expr.Compilation> + (call-with-input-string + string + (fun ((p <in-port>)) + (! set-path p + (format "~s" + `(buffer ,buffer offset ,offset str ,string))) + (!s kawa.lang.CompileFile read p m)))))) + (let ((o (@ currentOptions c))) + (! set o "warn-invoke-unknown-method" #t) + (! set o "warn-undefined-variable" #t)) + (let ((m (! getModule c))) + (! set-name m (format "<emacs>:~a/~a" buffer (current-time)))) + c)) + "/tmp/kawa-tmp.zip" env #t)) + +(df compiler-notes-for-emacs ((messages <gnu.text.SourceMessages>)) + (packing (pack) + (do ((e (! get-errors messages) (@ next e))) + ((nul? e)) + (pack (source-error>elisp e))))) + +(df source-error>elisp ((e <source-error>) => <list>) + (list ':message (to-string (@ message e)) + ':severity (case (integer->char (@ severity e)) + ((#\e #\f) ':error) + ((#\w) ':warning) + (else ':note)) + ':location (error-loc>elisp e))) + +(df error-loc>elisp ((e <source-error>)) + (cond ((nul? (@ filename e)) `(:error "No source location")) + ((! starts-with (@ filename e) "(buffer ") + (mlet (('buffer b 'offset ('quote ((:position o) _)) 'str s) + (read-from-string (@ filename e))) + (let ((off (line>offset (1- (@ line e)) s)) + (col (1- (@ column e)))) + `(:location (:buffer ,b) (:position ,(+ o off col)) nil)))) + (#t + `(:location (:file ,(to-string (@ filename e))) + (:line ,(@ line e) ,(1- (@ column e))) + nil)))) + +(df line>offset ((line <int>) (s <str>) => <int>) + (let ((offset :: <int> 0)) + (dotimes (i line) + (set offset (! index-of s (as <char> #\newline) offset)) + (assert (>= offset 0)) + (set offset (as <int> (+ offset 1)))) + (log "line=~a offset=~a\n" line offset) + offset)) + +(defslimefun load-file (env filename) + (format "Loaded: ~a => ~s" filename (eval `(load ,filename) env))) + +;;;; Completion + +(defslimefun simple-completions (env (pattern <str>) _) + (let* ((env (as <gnu.mapping.InheritingEnvironment> env)) + (matches (packing (pack) + (let ((iter (! enumerate-all-locations env))) + (while (! has-next iter) + (let ((l (! next-location iter))) + (typecase l + (<gnu.mapping.NamedLocation> + (let ((name (!! get-name get-key-symbol l))) + (when (! starts-with name pattern) + (pack name))))))))))) + `(,matches ,(cond ((null? matches) pattern) + (#t (fold+ common-prefix matches)))))) + +(df common-prefix ((s1 <str>) (s2 <str>) => <str>) + (let ((limit (min (! length s1) (! length s2)))) + (let loop ((i 0)) + (cond ((or (= i limit) + (not (== (! char-at s1 i) + (! char-at s2 i)))) + (! substring s1 0 i)) + (#t (loop (1+ i))))))) + +(df fold+ (f list) + (let loop ((s (car list)) + (l (cdr list))) + (cond ((null? l) s) + (#t (loop (f s (car l)) (cdr l)))))) + +;;; Quit + +(defslimefun quit-lisp (env) + (exit)) + +;;(defslimefun set-default-directory (env newdir)) + + +;;;; Dummy defs + +(defslimefun buffer-first-change (#!rest y) '()) +(defslimefun swank-require (#!rest y) '()) +(defslimefun frame-package-name (#!rest y) '()) + +;;;; arglist + +(defslimefun operator-arglist (env name #!rest _) + (mcase (try-catch `(ok ,(eval (read-from-string name) env)) + (ex <throwable> 'nil)) + (('ok obj) + (mcase (arglist obj) + ('#f 'nil) + ((args rtype) + (format "(~a~{~^ ~a~})~a" name + (map (fun (e) + (if (equal (cadr e) "java.lang.Object") (car e) e)) + args) + (if (equal rtype "java.lang.Object") + "" + (format " => ~a" rtype)))))) + (_ 'nil))) + +(df arglist (obj) + (typecase obj + (<gnu.expr.ModuleMethod> + (let* ((mref (module-method>meth-ref obj))) + (list (mapi (! arguments mref) + (fun ((v <local-var>)) + (list (! name v) (! typeName v)))) + (! returnTypeName mref)))) + (<object> #f))) + +;;;; M-. + +(defslimefun find-definitions-for-emacs (env name) + (mcase (try-catch `(ok ,(eval (read-from-string name) env)) + (ex <throwable> `(error ,(exception-message ex)))) + (('ok obj) (mapi (all-definitions obj) + (fun (d) + `(,(format "~a" d) ,(src-loc>elisp (src-loc d)))))) + (('error msg) `((,name (:error ,msg)))))) + +(define-simple-class <swank-location> (<location>) + (file #:init #f) + (line #:init #f) + ((*init* file name) + (set (@ file (this)) file) + (set (@ line (this)) line)) + ((lineNumber) :: <int> (or line (absent))) + ((lineNumber (s :: <str>)) :: int (! lineNumber (this))) + ((method) :: <meth-ref> (absent)) + ((sourcePath) :: <str> (or file (absent))) + ((sourcePath (s :: <str>)) :: <str> (! sourcePath (this))) + ((sourceName) :: <str> (absent)) + ((sourceName (s :: <str>)) :: <str> (! sourceName (this))) + ((declaringType) :: <ref-type> (absent)) + ((codeIndex) :: <long> -1) + ((virtualMachine) :: <vm> *the-vm*) + ((compareTo o) :: <int> + (typecase o + (<location> (- (! codeIndex (this)) (! codeIndex o)))))) + +(df absent () (primitive-throw (<absent-exc>))) + +(df all-definitions (o) + (typecase o + (<gnu.expr.ModuleMethod> (list o)) + (<gnu.expr.PrimProcedure> (list o)) + (<gnu.expr.GenericProc> (append (mappend all-definitions (gf-methods o)) + (let ((s (! get-setter o))) + (if s (all-definitions s) '())))) + (<java.lang.Class> (list o)) + (<gnu.mapping.Procedure> (all-definitions (! get-class o))) + (<kawa.lang.Macro> (list o)) + (<gnu.bytecode.ObjectType> (all-definitions (! getReflectClass o))) + (<java.lang.Object> '()) + )) + +(df gf-methods ((f <gnu.expr.GenericProc>)) + (let* ((o :: <obj-ref> (vm-mirror *the-vm* f)) + (f (! field-by-name (! reference-type o) "methods")) + (ms (vm-demirror *the-vm* (! get-value o f)))) + (filter (array-to-list ms) (fun (x) (not (nul? x)))))) + +(df src-loc (o => <location>) + (typecase o + (<gnu.expr.PrimProcedure> (src-loc (@ method o))) + (<gnu.expr.ModuleMethod> (module-method>src-loc o)) + (<gnu.expr.GenericProc> (<swank-location> #f #f)) + (<java.lang.Class> (class>src-loc o)) + (<kawa.lang.Macro> (<swank-location> #f #f)) + (<gnu.bytecode.Method> (bytemethod>src-loc o)))) + +(df module-method>src-loc ((f <gnu.expr.ModuleMethod>)) + (! location (module-method>meth-ref f))) + +(df module-method>meth-ref ((f <gnu.expr.ModuleMethod>) => <meth-ref>) + (let* ((module (! reference-type + (as <obj-ref> (vm-mirror *the-vm* (@ module f))))) + (1st-method-by-name (fun (name) + (let ((i (! methods-by-name module name))) + (cond ((! is-empty i) #f) + (#t (1st i))))))) + (as <meth-ref> (or (1st-method-by-name (! get-name f)) + (let ((mangled (mangled-name f))) + (or (1st-method-by-name mangled) + (1st-method-by-name (cat mangled "$V")) + (1st-method-by-name (cat mangled "$X")))))))) + +(df mangled-name ((f <gnu.expr.ModuleMethod>)) + (let* ((name0 (! get-name f)) + (name (cond ((nul? name0) (format "lambda~d" (@ selector f))) + (#t (!s gnu.expr.Compilation mangleName name0))))) + name)) + +(df class>src-loc ((c <java.lang.Class>) => <location>) + (let* ((type (class>ref-type c)) + (locs (! all-line-locations type))) + (cond ((not (! isEmpty locs)) (1st locs)) + (#t (<swank-location> (1st (! source-paths type "Java")) + #f))))) + +(df class>ref-type ((class <java.lang.Class>) => <ref-type>) + (! reflectedType (as <com.sun.jdi.ClassObjectReference> + (vm-mirror *the-vm* class)))) + +(df class>class-type ((class <java.lang.Class>) => <class-type>) + (as <class-type> (class>ref-type class))) + +(df bytemethod>src-loc ((m <gnu.bytecode.Method>) => <location>) + (let* ((cls (class>class-type (! get-reflect-class + (! get-declaring-class m)))) + (name (! get-name m)) + (sig (! get-signature m)) + (meth (! concrete-method-by-name cls name sig))) + (! location meth))) + +(df src-loc>elisp ((l <location>)) + (df src-loc>list ((l <location>)) + (list (ignore-errors (! source-name l "Java")) + (ignore-errors (! source-path l "Java")) + (ignore-errors (! line-number l "Java")))) + (mcase (src-loc>list l) + ((name path line) + (cond ((not path) + `(:error ,(call-with-abort (fun () (! source-path l))))) + ((! starts-with (as <str> path) "(buffer ") + (mlet (('buffer b 'offset o 'str s) (read-from-string path)) + `(:location (:buffer ,b) + (:position ,(+ o (line>offset line s))) + nil))) + (#t + `(:location ,(or (find-file-in-path name (source-path)) + (find-file-in-path path (source-path)) + (ferror "Can't find source-path: ~s ~s ~a" + path name (source-path))) + (:line ,(or line -1)) ())))))) + +(df src-loc>str ((l <location>)) + (cond ((nul? l) "<null-location>") + (#t (format "~a ~a ~a" + (or (ignore-errors (! source-path l)) + (ignore-errors (! source-name l)) + (ignore-errors (!! name declaring-type l))) + (ignore-errors (!! name method l)) + (ignore-errors (! lineNumber l)))))) + +;;;;;; class-path hacking + +;; (find-file-in-path "kawa/lib/kawa/hashtable.scm" (source-path)) + +(df find-file-in-path ((filename <str>) (path <list>)) + (let ((f (<file> filename))) + (cond ((! isAbsolute f) `(:file ,filename)) + (#t (let ((result #f)) + (find-if path (fun (dir) + (let ((x (find-file-in-dir f dir))) + (set result x))) + #f) + result))))) + +(df find-file-in-dir ((file <file>) (dir <str>)) + (let ((filename :: <str> (! getPath file))) + (or (let ((child (<file> (<file> dir) filename))) + (and (! exists child) + `(:file ,(! getPath child)))) + (try-catch + (and (not (nul? (! getEntry (<java.util.zip.ZipFile> dir) filename))) + `(:zip ,dir ,filename)) + (ex <throwable> #f))))) + +(define swank-java-source-path + (let* ((jre-home :: <str> (!s <java.lang.System> getProperty "java.home")) + (parent :: <str> (! get-parent (<file> jre-home)))) + (list (! get-path (<file> parent "src.zip"))))) + +(df source-path () + (mlet ((base) (search-path-prop "user.dir")) + (append + (list base) + (map (fun ((s <str>)) + (let ((f (<file> s)) + (base :: <str> (as <str> base))) + (cond ((! isAbsolute f) s) + (#t (! getPath (<file> base s)))))) + (class-path)) + swank-java-source-path))) + +(df class-path () + (append (search-path-prop "java.class.path") + (search-path-prop "sun.boot.class.path"))) + +(df search-path-prop ((name <str>)) + (array-to-list (! split (!s java.lang.System getProperty name) + (@s <file> pathSeparator)))) + +;;;; Disassemble + +(defslimefun disassemble-form (env form) + (mcase (read-from-string form) + (('quote name) + (let ((f (eval name env))) + (typecase f + (<gnu.expr.ModuleMethod> + (disassemble-to-string (module-method>meth-ref f)))))))) + +(df disassemble-to-string ((mr <meth-ref>) => <str>) + (with-sink #f (fun (out) (disassemble-meth-ref mr out)))) + +(df disassemble-meth-ref ((mr <meth-ref>) (out <java.io.PrintWriter>)) + (let* ((t (! declaring-type mr))) + (disas-header mr out) + (disas-code (! constant-pool t) + (! constant-pool-count t) + (! bytecodes mr) + out))) + +(df disas-header ((mr <meth-ref>) (out <java.io.PrintWriter>)) + (let* ((++ (fun ((str <str>)) (! write out str))) + (? (fun (flag str) (if flag (++ str))))) + (? (! is-static mr) "static ") + (? (! is-final mr) "final ") + (? (! is-private mr) "private ") + (? (! is-protected mr) "protected ") + (? (! is-public mr) "public ") + (++ (! name mr)) (++ (! signature mr)) (++ "\n"))) + +(df disas-code ((cpool <byte[]>) (cpoolcount <int>) (bytecode <byte[]>) + (out <java.io.PrintWriter>)) + (let* ((ct (<gnu.bytecode.ClassType> "foo")) + (met (! addMethod ct "bar" 0)) + (ca (<gnu.bytecode.CodeAttr> met)) + (constants (let* ((bs (<java.io.ByteArrayOutputStream>)) + (s (<java.io.DataOutputStream> bs))) + (! write-short s cpoolcount) + (! write s cpool) + (! flush s) + (! toByteArray bs)))) + (vm-set-slot *the-vm* ct "constants" + (<gnu.bytecode.ConstantPool> + (<java.io.DataInputStream> + (<java.io.ByteArrayInputStream> + constants)))) + (! setCode ca bytecode) + (let ((w (<gnu.bytecode.ClassTypeWriter> ct out 0))) + (! print ca w) + (! flush w)))) + +(df with-sink (sink (f <function>)) + (cond ((instance? sink <java.io.PrintWriter>) (f sink)) + ((== sink #t) (f (as <java.io.PrintWriter> (current-output-port)))) + ((== sink #f) + (let* ((buffer (<java.io.StringWriter>)) + (out (<java.io.PrintWriter> buffer))) + (f out) + (! flush out) + (! toString buffer))) + (#t (ferror "Invalid sink designator: ~s" sink)))) + +(df test-disas ((c <str>) (m <str>)) + (let* ((vm (as <vm> *the-vm*)) + (c (as <ref-type> (1st (! classes-by-name vm c)))) + (m (as <meth-ref> (1st (! methods-by-name c m))))) + (with-sink #f (fun (out) (disassemble-meth-ref m out))))) + +;; (test-disas "java.lang.Class" "toString") + + +;;;; Macroexpansion + +(defslimefun swank-expand-1 (env s) (%swank-macroexpand s env)) +(defslimefun swank-expand (env s) (%swank-macroexpand s env)) +(defslimefun swank-expand-all (env s) (%swank-macroexpand s env)) + +(df %swank-macroexpand (string env) + (pprint-to-string (%macroexpand (read-from-string string) env))) + +(df %macroexpand (sexp env) (expand sexp #:env env)) + + +;;;; Inspector + +(define-simple-class <inspector-state> () + (object #:init #!null) + (parts :: <java.util.ArrayList> #:init (<java.util.ArrayList>) ) + (stack :: <list> #:init '()) + (content :: <list> #:init '())) + +(df make-inspector (env (vm <vm>) => <chan>) + (car (spawn/chan (fun (c) (inspector c env vm))))) + +(df inspector ((c <chan>) env (vm <vm>)) + (! set-name (current-thread) "inspector") + (let ((state :: <inspector-state> (<inspector-state>)) + (open #t)) + (while open + (mcase (recv c) + (('init str id) + (set state (<inspector-state>)) + (let ((obj (try-catch (eval (read-from-string str) env) + (ex <throwable> ex)))) + (reply c (inspect-object obj state vm) id))) + (('init-mirror cc id) + (set state (<inspector-state>)) + (let* ((mirror (recv cc)) + (obj (vm-demirror vm mirror))) + (reply c (inspect-object obj state vm) id))) + (('inspect-part n id) + (let ((part (! get (@ parts state) n))) + (reply c (inspect-object part state vm) id))) + (('pop id) + (reply c (inspector-pop state vm) id)) + (('quit id) + (reply c 'nil id) + (set open #f)))))) + +(df inspect-object (obj (state <inspector-state>) (vm <vm>)) + (set (@ object state) obj) + (set (@ parts state) (<java.util.ArrayList>)) + (pushf obj (@ stack state)) + (set (@ content state) (inspector-content + `("class: " (:value ,(! getClass obj)) "\n" + ,@(inspect obj vm)) + state)) + (cond ((nul? obj) (list ':title "#!null" ':id 0 ':content `())) + (#t + (list ':title (pprint-to-string obj) + ':id (assign-index obj state) + ':content (let ((c (@ content state))) + (content-range c 0 (len c))))))) + +(df inspect (obj vm) + (let ((obj (as <obj-ref> (vm-mirror vm obj)))) + (typecase obj + (<array-ref> (inspect-array-ref vm obj)) + (<obj-ref> (inspect-obj-ref vm obj))))) + +(df inspect-array-ref ((vm <vm>) (obj <array-ref>)) + (packing (pack) + (let ((i 0)) + (for (((v :: <value>) (! getValues obj))) + (pack (format "~d: " i)) + (pack `(:value ,(vm-demirror vm v))) + (pack "\n") + (set i (1+ i)))))) + +(df inspect-obj-ref ((vm <vm>) (obj <obj-ref>)) + (let* ((type (! referenceType obj)) + (fields (! allFields type)) + (values (! getValues obj fields)) + (ifields '()) (sfields '()) (imeths '()) (smeths '()) + (frob (lambda (lists) (apply append (reverse lists))))) + (for (((f :: <field>) fields)) + (let* ((val (as <value> (! get values f))) + (l `(,(! name f) ": " (:value ,(vm-demirror vm val)) "\n"))) + (if (! is-static f) + (pushf l sfields) + (pushf l ifields)))) + (for (((m :: <meth-ref>) (! allMethods type))) + (let ((l `(,(! name m) ,(! signature m) "\n"))) + (if (! is-static m) + (pushf l smeths) + (pushf l imeths)))) + `(,@(frob ifields) + "--- static fields ---\n" ,@(frob sfields) + "--- methods ---\n" ,@(frob imeths) + "--- static methods ---\n" ,@(frob smeths)))) + +(df inspector-content (content (state <inspector-state>)) + (map (fun (part) + (mcase part + ((':value val) + `(:value ,(pprint-to-string val) ,(assign-index val state))) + (x (to-string x)))) + content)) + +(df assign-index (obj (state <inspector-state>) => <int>) + (! add (@ parts state) obj) + (1- (! size (@ parts state)))) + +(df content-range (l start end) + (let* ((len (length l)) (end (min len end))) + (list (subseq l start end) len start end))) + +(df inspector-pop ((state <inspector-state>) vm) + (cond ((<= 2 (len (@ stack state))) + (let ((obj (cadr (@ stack state)))) + (set (@ stack state) (cddr (@ stack state))) + (inspect-object obj state vm))) + (#t 'nil))) + +;;;; IO redirection + +(define-simple-class <swank-writer> (<java.io.Writer>) + (q :: <queue> #:init (<queue> (as <int> 100))) + ((*init*) (invoke-special <java.io.Writer> (this) '*init*)) + ((write (buffer :: <char[]>) (from :: <int>) (to :: <int>)) :: <void> + (synchronized (this) + (assert (not (== q #!null))) + (! put q `(write ,(<str> buffer from to))))) + ((close) :: <void> + (synchronized (this) + (! put q 'close) + (set! q #!null))) + ((flush) :: <void> + (synchronized (this) + (assert (not (== q #!null))) + (let ((ex (<exchanger>))) + (! put q `(flush ,ex)) + (! exchange ex #!null))))) + +(df swank-writer ((in <chan>) (q <queue>)) + (! set-name (current-thread) "swank-redirect-thread") + (let* ((out (as <chan> (recv in))) + (builder (<builder>)) + (flush (fun () + (unless (zero? (! length builder)) + (send out `(forward (:write-string ,(<str> builder)))) + (! setLength builder 0)))) + (closed #f)) + (while (not closed) + (mcase (! poll q (as long 200) (@s <timeunit> MILLISECONDS)) + ('#!null (flush)) + (('write s) + (! append builder (as <str> s)) + (when (> (! length builder) 4000) + (flush))) + (('flush ex) + (flush) + (! exchange (as <exchanger> ex) #!null)) + ('close + (set closed #t) + (flush)))))) + +(df make-swank-outport ((out <chan>)) + (let ((w (<swank-writer>))) + (mlet ((in . _) (spawn/chan (fun (c) (swank-writer c (@ q w))))) + (send in out)) + (<out-port> w #t #t))) + + +;;;; Monitor + +;;(define-simple-class <monitorstate> () +;; (threadmap type: (tab))) + +(df vm-monitor ((c <chan>)) + (! set-name (current-thread) "swank-vm-monitor") + (let ((vm (vm-attach))) + (log-vm-props vm) + (request-breakpoint vm) + (mlet* (((ev . _) (spawn/chan/catch + (fun (c) + (let ((q (! eventQueue vm))) + (while #t + (send c `(vm-event ,(to-list (! remove q))))))))) + (to-string (vm-to-string vm)) + (state (tab))) + (send c `(publish-vm ,vm)) + (while #t + (mcase (recv* (list c ev)) + ((_ . ('get-vm cc)) + (send cc vm)) + ((,c . ('debug-info thread from to id)) + (reply c (debug-info thread from to state) id)) + ((,c . ('throw-to-toplevel thread id)) + (set state (throw-to-toplevel thread id c state))) + ((,c . ('thread-continue thread id)) + (set state (thread-continue thread id c state))) + ((,c . ('frame-src-loc thread frame id)) + (reply c (frame-src-loc thread frame state) id)) + ((,c . ('frame-details thread frame id)) + (reply c (list (frame-locals thread frame state) '()) id)) + ((,c . ('disassemble-frame thread frame id)) + (reply c (disassemble-frame thread frame state) id)) + ((,c . ('thread-frames thread from to id)) + (reply c (thread-frames thread from to state) id)) + ((,c . ('list-threads id)) + (reply c (list-threads vm state) id)) + ((,c . ('interrupt-thread ref)) + (set state (interrupt-thread ref state c))) + ((,c . ('debug-nth-thread n)) + (let ((t (nth (get state 'all-threads #f) n))) + ;;(log "thread ~d : ~a\n" n t) + (set state (interrupt-thread t state c)))) + ((,c . ('quit-thread-browser id)) + (reply c 't id) + (set state (del state 'all-threads))) + ((,ev . ('vm-event es)) + ;;(log "vm-events: len=~a\n" (len es)) + (for (((e :: <event>) (as <list> es))) + (set state (process-vm-event e c state)))) + ((_ . ('get-exception from tid)) + (mlet ((_ _ es) (get state tid #f)) + (send from (let ((e (car es))) + (typecase e + (<exception-event> (! exception e)) + (<event> e)))))) + ((_ . ('get-local rc tid frame var)) + (send rc (frame-local-var tid frame var state))) + ))))) + +(df reply ((c <chan>) value id) + (send c `(forward (:return (:ok ,value) ,id)))) + +(df reply-abort ((c <chan>) id) + (send c `(forward (:return (:abort nil) ,id)))) + +(df process-vm-event ((e <event>) (c <chan>) state) + ;;(log "vm-event: ~s\n" e) + (typecase e + (<exception-event> + ;;(log "exception: ~s\n" (! exception e)) + ;;(log "exception-message: ~s\n" + ;; (exception-message (vm-demirror *the-vm* (! exception e)))) + ;;(log "exception-location: ~s\n" (src-loc>str (! location e))) + ;;(log "exception-catch-location: ~s\n" (src-loc>str (! catch-location e))) + (cond ((! notifyUncaught (as <com.sun.jdi.request.ExceptionRequest> + (! request e))) + (process-exception e c state)) + (#t + (let* ((t (! thread e)) + (r (! request e)) + (ex (! exception e))) + (unless (eq? *last-exception* ex) + (set *last-exception* ex) + (set *last-stacktrace* (copy-stack t))) + (! resume t)) + state))) + (<step-event> + (let* ((r (! request e)) + (k (! get-property r 'continuation))) + (! disable r) + (log "k: ~s\n" k) + (k e)) + state) + (<breakpoint-event> + (log "breakpoint event: ~a\n" e) + (debug-thread (! thread e) e state c)) + )) + +(df process-exception ((e <exception-event>) (c <chan>) state) + (let* ((tref (! thread e)) + (tid (! uniqueID tref)) + (s (get state tid #f))) + (mcase s + ('#f + ;; XXX redundant in debug-thread + (let* ((level 1) + (state (put state tid (list tref level (list e))))) + (send c `(forward (:debug ,tid ,level + ,@(debug-info tid 0 15 state)))) + (send c `(forward (:debug-activate ,tid ,level))) + state)) + ((_ level exs) + (send c `(forward (:debug-activate ,(! uniqueID tref) ,level))) + (put state tid (list tref (1+ level) (cons e exs))))))) + +(define-simple-class <faked-frame> () + (loc :: <location>) + (args) + (names) + (values :: <java.util.Map>) + (self) + ((*init* (loc :: <location>) args names (values :: <java.util.Map>) self) + (set (@ loc (this)) loc) + (set (@ args (this)) args) + (set (@ names (this)) names) + (set (@ values (this)) values) + (set (@ self (this)) self)) + ((toString) :: <str> + (format "#<ff ~a>" (src-loc>str loc)))) + +(df copy-stack ((t <thread-ref>)) + (packing (pack) + (iter (! frames t) + (fun ((f <frame>)) + (let ((vars (ignore-errors (! visibleVariables f)))) + (pack (<faked-frame> + (or (ignore-errors (! location f)) #!null) + (ignore-errors (! getArgumentValues f)) + (or vars #!null) + (or (and vars (ignore-errors (! get-values f vars))) + #!null) + (ignore-errors (! thisObject f))))))))) + +(define-simple-class <interrupt-event> (<event>) + (thread :: <thread-ref>) + ((*init* (thread :: <thread-ref>)) (set (@ thread (this)) thread)) + ((request) :: <com.sun.jdi.request.EventRequest> #!null) + ((virtualMachine) :: <vm> (! virtualMachine thread))) + +(df break (#!optional condition) + ((breakpoint condition))) + +;; We set a breakpoint on this function. It returns a function which +;; specifies what the debuggee should do next (the actual return value +;; is set via JDI). Lets hope that the compiler doesn't optimize this +;; away. +(df breakpoint (condition => <function>) + (fun () #!null)) + +;; Enable breakpoints event on the breakpoint function. +(df request-breakpoint ((vm <vm>)) + (let* ((swank-classes (! classesByName vm "swank-kawa")) + (swank-classes-legacy (! classesByName vm "swank$Mnkawa")) + (class :: <class-type> (1st (if (= (length swank-classes) 0) + swank-classes-legacy + swank-classes))) + (meth :: <meth-ref> (1st (! methodsByName class "breakpoint"))) + (erm (! eventRequestManager vm)) + (req (! createBreakpointRequest erm (! location meth)))) + (! setSuspendPolicy req (@ SUSPEND_EVENT_THREAD req)) + (! put-property req 'swank #t) + (! put-property req 'argname "condition") + (! enable req))) + +(df log-vm-props ((vm <vm>)) + (letrec-syntax ((p (syntax-rules () + ((p name) (log "~s: ~s\n" 'name (! name vm))))) + (p* (syntax-rules () + ((p* n ...) (seq (p n) ...))))) + (p* canBeModified + canRedefineClasses + canAddMethod + canUnrestrictedlyRedefineClasses + canGetBytecodes + canGetConstantPool + canGetSyntheticAttribute + canGetSourceDebugExtension + canPopFrames + canForceEarlyReturn + canGetMethodReturnValues + canGetInstanceInfo + ))) + +;;;;; Debugger + +(df debug-thread ((tref <thread-ref>) (ev <event>) state (c <chan>)) + (unless (! is-suspended tref) + (! suspend tref)) + (let* ((id (! uniqueID tref)) + (level 1) + (state (put state id (list tref level (list ev))))) + (send c `(forward (:debug ,id ,level ,@(debug-info id 0 10 state)))) + (send c `(forward (:debug-activate ,id ,level))) + state)) + +(df interrupt-thread ((tref <thread-ref>) state (c <chan>)) + (debug-thread tref (<interrupt-event> tref) state c)) + +(df debug-info ((tid <int>) (from <int>) to state) + (mlet ((thread-ref level evs) (get state tid #f)) + (let* ((tref (as <thread-ref> thread-ref)) + (vm (! virtualMachine tref)) + (ev (as <event> (car evs))) + (ex (typecase ev + (<breakpoint-event> (breakpoint-condition ev)) + (<exception-event> (! exception ev)) + (<interrupt-event> (<java.lang.Exception> "Interrupt")))) + (desc (typecase ex + (<obj-ref> + ;;(log "ex: ~a ~a\n" ex (vm-demirror vm ex)) + (! toString (vm-demirror vm ex))) + (<java.lang.Throwable> (! toString ex)))) + (type (format " [type ~a]" + (typecase ex + (<obj-ref> (! name (! referenceType ex))) + (<object> (!! getName getClass ex))))) + (bt (thread-frames tid from to state))) + `((,desc ,type nil) (("quit" "terminate current thread")) ,bt ())))) + +(df breakpoint-condition ((e <breakpoint-event>) => <obj-ref>) + (let ((frame (! frame (! thread e) 0))) + (1st (! get-argument-values frame)))) + +(df thread-frames ((tid <int>) (from <int>) to state) + (mlet ((thread level evs) (get state tid #f)) + (let* ((thread (as <thread-ref> thread)) + (fcount (! frameCount thread)) + (stacktrace (event-stacktrace (car evs))) + (missing (cond ((zero? (len stacktrace)) 0) + (#t (- (len stacktrace) fcount)))) + (fstart (max (- from missing) 0)) + (flen (max (- to from missing) 0)) + (frames (! frames thread fstart (min flen (- fcount fstart))))) + (packing (pack) + (let ((i from)) + (dotimes (_ (max (- missing from) 0)) + (pack (list i (format "~a" (stacktrace i)))) + (set i (1+ i))) + (iter frames (fun ((f <frame>)) + (let ((s (frame-to-string f))) + (pack (list i s)) + (set i (1+ i)))))))))) + +(df event-stacktrace ((ev <event>)) + (let ((nothing (fun () (<java.lang.StackTraceElement[]>))) + (vm (! virtualMachine ev))) + (typecase ev + (<breakpoint-event> + (let ((condition (vm-demirror vm (breakpoint-condition ev)))) + (cond ((instance? condition <throwable>) + (throwable-stacktrace vm condition)) + (#t (nothing))))) + (<exception-event> + (throwable-stacktrace vm (vm-demirror vm (! exception ev)))) + (<event> (nothing))))) + +(df throwable-stacktrace ((vm <vm>) (ex <throwable>)) + (cond ((== ex (ignore-errors (vm-demirror vm *last-exception*))) + *last-stacktrace*) + (#t + (! getStackTrace ex)))) + +(df frame-to-string ((f <frame>)) + (let ((loc (! location f)) + (vm (! virtualMachine f))) + (format "~a (~a)" (!! name method loc) + (call-with-abort + (fun () (format "~{~a~^ ~}" + (mapi (! getArgumentValues f) + (fun (arg) + (pprint-to-string + (vm-demirror vm arg)))))))))) + +(df frame-src-loc ((tid <int>) (n <int>) state) + (try-catch + (mlet* (((frame vm) (nth-frame tid n state)) + (vm (as <vm> vm))) + (src-loc>elisp + (typecase frame + (<frame> (! location frame)) + (<faked-frame> (@ loc frame)) + (<java.lang.StackTraceElement> + (let* ((classname (! getClassName frame)) + (classes (! classesByName vm classname)) + (t (as <ref-type> (1st classes)))) + (1st (! locationsOfLine t (! getLineNumber frame)))))))) + (ex <throwable> + (let ((msg (! getMessage ex))) + `(:error ,(if (== msg #!null) + (! toString ex) + msg)))))) + +(df nth-frame ((tid <int>) (n <int>) state) + (mlet ((tref level evs) (get state tid #f)) + (let* ((thread (as <thread-ref> tref)) + (fcount (! frameCount thread)) + (stacktrace (event-stacktrace (car evs))) + (missing (cond ((zero? (len stacktrace)) 0) + (#t (- (len stacktrace) fcount)))) + (vm (! virtualMachine thread)) + (frame (cond ((< n missing) + (stacktrace n)) + (#t (! frame thread (- n missing)))))) + (list frame vm)))) + +;;;;; Locals + +(df frame-locals ((tid <int>) (n <int>) state) + (mlet ((thread _ _) (get state tid #f)) + (let* ((thread (as <thread-ref> thread)) + (vm (! virtualMachine thread)) + (p (fun (x) (pprint-to-string + (call-with-abort (fun () (vm-demirror vm x))))))) + (map (fun (x) + (mlet ((name value) x) + (list ':name name ':value (p value) ':id 0))) + (%frame-locals tid n state))))) + +(df frame-local-var ((tid <int>) (frame <int>) (var <int>) state => <mirror>) + (cadr (nth (%frame-locals tid frame state) var))) + +(df %frame-locals ((tid <int>) (n <int>) state) + (mlet ((frame _) (nth-frame tid n state)) + (typecase frame + (<frame> + (let* ((visible (try-catch (! visibleVariables frame) + (ex <com.sun.jdi.AbsentInformationException> + '()))) + (map (! getValues frame visible)) + (p (fun (x) x))) + (packing (pack) + (let ((self (ignore-errors (! thisObject frame)))) + (when self + (pack (list "this" (p self))))) + (iter (! entrySet map) + (fun ((e <java.util.Map$Entry>)) + (let ((var (as <local-var> (! getKey e))) + (val (as <value> (! getValue e)))) + (pack (list (! name var) (p val))))))))) + (<faked-frame> + (packing (pack) + (when (@ self frame) + (pack (list "this" (@ self frame)))) + (iter (! entrySet (@ values frame)) + (fun ((e <java.util.Map$Entry>)) + (let ((var (as <local-var> (! getKey e))) + (val (as <value> (! getValue e)))) + (pack (list (! name var) val))))))) + (<java.lang.StackTraceElement> '())))) + +(df disassemble-frame ((tid <int>) (frame <int>) state) + (mlet ((frame _) (nth-frame tid frame state)) + (typecase frame + (<java.lang.StackTraceElement> "<??>") + (<frame> + (let* ((l (! location frame)) + (m (! method l)) + (c (! declaringType l))) + (disassemble-to-string m)))))) + +;;;;; Restarts + +;; FIXME: factorize +(df throw-to-toplevel ((tid <int>) (id <int>) (c <chan>) state) + (mlet ((tref level exc) (get state tid #f)) + (let* ((t (as <thread-ref> tref)) + (ev (car exc))) + (typecase ev + (<exception-event> ; actually uncaughtException + (! resume t) + (reply-abort c id) + ;;(send-debug-return c tid state) + (do ((level level (1- level)) + (exc exc (cdr exc))) + ((null? exc)) + (send c `(forward (:debug-return ,tid ,level nil)))) + (del state tid)) + (<breakpoint-event> + ;; XXX race condition? + (log "resume from from break (suspendCount: ~d)\n" (! suspendCount t)) + (let ((vm (! virtualMachine t)) + (k (fun () (primitive-throw (<listener-abort>))))) + (reply-abort c id) + (! force-early-return t (vm-mirror vm k)) + (! resume t) + (do ((level level (1- level)) + (exc exc (cdr exc))) + ((null? exc)) + (send c `(forward (:debug-return ,tid ,level nil)))) + (del state tid))) + (<interrupt-event> + (log "resume from from interrupt\n") + (let ((vm (! virtualMachine t))) + (! stop t (vm-mirror vm (<listener-abort>))) + (! resume t) + (reply-abort c id) + (do ((level level (1- level)) + (exc exc (cdr exc))) + ((null? exc)) + (send c `(forward (:debug-return ,tid ,level nil)))) + (del state tid)) + ))))) + +(df thread-continue ((tid <int>) (id <int>) (c <chan>) state) + (mlet ((tref level exc) (get state tid #f)) + (log "thread-continue: ~a ~a ~a \n" tref level exc) + (let* ((t (as <thread-ref> tref))) + (! resume t)) + (reply-abort c id) + (do ((level level (1- level)) + (exc exc (cdr exc))) + ((null? exc)) + (send c `(forward (:debug-return ,tid ,level nil)))) + (del state tid))) + +(df thread-step ((t <thread-ref>) k) + (let* ((vm (! virtual-machine t)) + (erm (! eventRequestManager vm)) + (<sr> <com.sun.jdi.request.StepRequest>) + (req (! createStepRequest erm t + (@s <sr> STEP_MIN) + (@s <sr> STEP_OVER)))) + (! setSuspendPolicy req (@ SUSPEND_EVENT_THREAD req)) + (! addCountFilter req 1) + (! put-property req 'continuation k) + (! enable req))) + +(df eval-in-thread ((t <thread-ref>) sexp + #!optional (env :: <env> (!s <env> current))) + (let* ((vm (! virtualMachine t)) + (sc :: <class-type> + (1st (! classes-by-name vm "kawa.standard.Scheme"))) + (ev :: <meth-ref> + (1st (! methods-by-name sc "eval" + (cat "(Ljava/lang/Object;Lgnu/mapping/Environment;)" + "Ljava/lang/Object;"))))) + (! invokeMethod sc t ev (list sexp env) + (@s <class-type> INVOKE_SINGLE_THREADED)))) + +;;;;; Threads + +(df list-threads (vm :: <vm> state) + (let* ((threads (! allThreads vm))) + (put state 'all-threads threads) + (packing (pack) + (pack '(\:id \:name \:status \:priority)) + (iter threads (fun ((t <thread-ref>)) + (pack (list (! uniqueID t) + (! name t) + (let ((s (thread-status t))) + (if (! is-suspended t) + (cat "SUSPENDED/" s) + s)) + 0))))))) + +(df thread-status (t :: <thread-ref>) + (let ((s (! status t))) + (cond ((= s (@s <thread-ref> THREAD_STATUS_UNKNOWN)) "UNKNOWN") + ((= s (@s <thread-ref> THREAD_STATUS_ZOMBIE)) "ZOMBIE") + ((= s (@s <thread-ref> THREAD_STATUS_RUNNING)) "RUNNING") + ((= s (@s <thread-ref> THREAD_STATUS_SLEEPING)) "SLEEPING") + ((= s (@s <thread-ref> THREAD_STATUS_MONITOR)) "MONITOR") + ((= s (@s <thread-ref> THREAD_STATUS_WAIT)) "WAIT") + ((= s (@s <thread-ref> THREAD_STATUS_NOT_STARTED)) "NOT_STARTED") + (#t "<bug>")))) + +;;;;; Bootstrap + +(df vm-attach (=> <vm>) + (attach (getpid) 20)) + +(df attach (pid timeout) + (log "attaching: ~a ~a\n" pid timeout) + (let* ((<ac> <com.sun.jdi.connect.AttachingConnector>) + (<arg> <com.sun.jdi.connect.Connector$Argument>) + (vmm (!s com.sun.jdi.Bootstrap virtualMachineManager)) + (pa (as <ac> + (or + (find-if (! attaching-connectors vmm) + (fun (x :: <ac>) + (! equals (! name x) "com.sun.jdi.ProcessAttach")) + #f) + (error "ProcessAttach connector not found")))) + (args (! default-arguments pa))) + (! set-value (as <arg> (! get args (to-str "pid"))) pid) + (when timeout + (! set-value (as <arg> (! get args (to-str "timeout"))) timeout)) + (log "attaching2: ~a ~a\n" pa args) + (! attach pa args))) + +(df getpid () + (let ((p (make-process (command-parse "echo $PPID") #!null))) + (! waitFor p) + (! read-line (<java.io.BufferedReader> (<in> (! get-input-stream p)))))) + +(df request-uncaught-exception-events ((vm <vm>)) + (let* ((erm (! eventRequestManager vm)) + (req (! createExceptionRequest erm #!null #f #t))) + (! setSuspendPolicy req (@ SUSPEND_EVENT_THREAD req)) + (! addThreadFilter req (vm-mirror vm (current-thread))) + (! enable req))) + + +(df request-caught-exception-events ((vm <vm>)) + (let* ((erm (! eventRequestManager vm)) + (req (! createExceptionRequest erm #!null #t #f))) + (! setSuspendPolicy req (@ SUSPEND_EVENT_THREAD req)) + (! addThreadFilter req (vm-mirror vm (current-thread))) + (! addClassExclusionFilter req "java.lang.ClassLoader") + (! addClassExclusionFilter req "java.net.URLClassLoader") + (! addClassExclusionFilter req "java.net.URLClassLoader$1") + (! enable req))) + +(df set-stacktrace-recording ((vm <vm>) (flag <boolean>)) + (for (((e :: <com.sun.jdi.request.ExceptionRequest>) + (!! exceptionRequests eventRequestManager vm))) + (when (! notify-caught e) + (! setEnabled e flag)))) + +;; (set-stacktrace-recording *the-vm* #f) + +(df vm-to-string ((vm <vm>)) + (let* ((obj (as <ref-type> (1st (! classesByName vm "java.lang.Object")))) + (met (as <meth-ref> (1st (! methodsByName obj "toString"))))) + (fun ((o <obj-ref>) (t <thread-ref>)) + (! value + (as <str-ref> + (! invokeMethod o t met '() + (@s <obj-ref> INVOKE_SINGLE_THREADED))))))) + +(define-simple-class <swank-global-variable> () + (var #:allocation 'static)) + +(define-variable *global-get-mirror* #!null) +(define-variable *global-set-mirror* #!null) +(define-variable *global-get-raw* #!null) +(define-variable *global-set-raw* #!null) + +(df init-global-field ((vm <vm>)) + (when (nul? *global-get-mirror*) + (set (@s <swank-global-variable> var) #!null) ; prepare class + (let* ((swank-global-variable-classes + (! classes-by-name vm "swank-global-variable")) + (swank-global-variable-classes-legacy + (! classes-by-name vm "swank$Mnglobal$Mnvariable")) + (c (as <com.sun.jdi.ClassType> + (1st (if (= (length swank-global-variable-classes) 0) + swank-global-variable-classes-legacy + swank-global-variable-classes)))) + (f (! fieldByName c "var"))) + (set *global-get-mirror* (fun () (! getValue c f))) + (set *global-set-mirror* (fun ((v <obj-ref>)) (! setValue c f v)))) + (set *global-get-raw* (fun () '() (@s <swank-global-variable> var))) + (set *global-set-raw* (fun (x) + (set (@s <swank-global-variable> var) x))))) + +(df vm-mirror ((vm <vm>) obj) + (synchronized vm + (init-global-field vm) + (*global-set-raw* obj) + (*global-get-mirror*))) + +(df vm-demirror ((vm <vm>) (v <value>)) + (synchronized vm + (if (== v #!null) + #!null + (typecase v + (<obj-ref> (init-global-field vm) + (*global-set-mirror* v) + (*global-get-raw*)) + (<com.sun.jdi.IntegerValue> (! value v)) + (<com.sun.jdi.LongValue> (! value v)) + (<com.sun.jdi.CharValue> (! value v)) + (<com.sun.jdi.ByteValue> (! value v)) + (<com.sun.jdi.BooleanValue> (! value v)) + (<com.sun.jdi.ShortValue> (! value v)) + (<com.sun.jdi.FloatValue> (! value v)) + (<com.sun.jdi.DoubleValue> (! value v)))))) + +(df vm-set-slot ((vm <vm>) (o <object>) (name <str>) value) + (let* ((o (as <obj-ref> (vm-mirror vm o))) + (t (! reference-type o)) + (f (! field-by-name t name))) + (! set-value o f (vm-mirror vm value)))) + +(define-simple-class <ucex-handler> + (<java.lang.Thread$UncaughtExceptionHandler>) + (f :: <gnu.mapping.Procedure>) + ((*init* (f :: <gnu.mapping.Procedure>)) (set (@ f (this)) f)) + ((uncaughtException (t :: <thread>) (e :: <throwable>)) + :: <void> + (! println (@s java.lang.System err) (to-str "uhexc:::")) + (! apply2 f t e) + #!void)) + +;;;; Channels + +(df spawn (f) + (let ((thread (<thread> (%%runnable f)))) + (! start thread) + thread)) + + +;; gnu.mapping.RunnableClosure uses the try{...}catch(Throwable){...} +;; idiom which defeats all attempts to use a break-on-error-style +;; debugger. Previously I had my own version of RunnableClosure +;; without that deficiency but something in upstream changed and it no +;; longer worked. Now we use the normal RunnableClosure and at the +;; cost of taking stack snapshots on every throw. +(df %%runnable (f => <java.lang.Runnable>) + ;;(<runnable> f) + ;;(<gnu.mapping.RunnableClosure> f) + ;;(runnable f) + (%runnable f) + ) + +(df %runnable (f => <java.lang.Runnable>) + (runnable + (fun () + (try-catch (f) + (ex <throwable> + (log "exception in thread ~s: ~s" (current-thread) + ex) + (! printStackTrace ex)))))) + +(df chan () + (let ((lock (<object>)) + (im (<chan>)) + (ex (<chan>))) + (set (@ lock im) lock) + (set (@ lock ex) lock) + (set (@ peer im) ex) + (set (@ peer ex) im) + (cons im ex))) + +(df immutable? (obj) + (or (== obj #!null) + (symbol? obj) + (number? obj) + (char? obj) + (instance? obj <str>) + (null? obj))) + +(df send ((c <chan>) value => <void>) + (df pass (obj) + (cond ((immutable? obj) obj) + ((string? obj) (! to-string obj)) + ((pair? obj) + (let loop ((r (list (pass (car obj)))) + (o (cdr obj))) + (cond ((null? o) (reverse! r)) + ((pair? o) (loop (cons (pass (car o)) r) (cdr o))) + (#t (append (reverse! r) (pass o)))))) + ((instance? obj <chan>) + (let ((o :: <chan> obj)) + (assert (== (@ owner o) (current-thread))) + (synchronized (@ lock c) + (set (@ owner o) (@ owner (@ peer c)))) + o)) + ((or (instance? obj <env>) + (instance? obj <mirror>)) + ;; those can be shared, for pragmatic reasons + obj + ) + (#t (error "can't send" obj (class-name-sans-package obj))))) + ;;(log "send: ~s ~s -> ~s\n" value (@ owner c) (@ owner (@ peer c))) + (assert (== (@ owner c) (current-thread))) + ;;(log "lock: ~s send\n" (@ owner (@ peer c))) + (synchronized (@ owner (@ peer c)) + (! put (@ queue (@ peer c)) (pass value)) + (! notify (@ owner (@ peer c)))) + ;;(log "unlock: ~s send\n" (@ owner (@ peer c))) + ) + +(df recv ((c <chan>)) + (cdr (recv/timeout (list c) 0))) + +(df recv* ((cs <iterable>)) + (recv/timeout cs 0)) + +(df recv/timeout ((cs <iterable>) (timeout <long>)) + (let ((self (current-thread)) + (end (if (zero? timeout) + 0 + (+ (current-time) timeout)))) + ;;(log "lock: ~s recv\n" self) + (synchronized self + (let loop () + ;;(log "receive-loop: ~s\n" self) + (let ((ready (find-if cs + (fun ((c <chan>)) + (not (! is-empty (@ queue c)))) + #f))) + (cond (ready + ;;(log "unlock: ~s recv\n" self) + (cons ready (! take (@ queue (as <chan> ready))))) + ((zero? timeout) + ;;(log "wait: ~s recv\n" self) + (! wait self) (loop)) + (#t + (let ((now (current-time))) + (cond ((<= end now) + 'timeout) + (#t + ;;(log "wait: ~s recv\n" self) + (! wait self (- end now)) + (loop))))))))))) + +(df rpc ((c <chan>) msg) + (mlet* (((im . ex) (chan)) + ((op . args) msg)) + (send c `(,op ,ex . ,args)) + (recv im))) + +(df spawn/chan (f) + (mlet ((im . ex) (chan)) + (let ((thread (<thread> (%%runnable (fun () (f ex)))))) + (set (@ owner ex) thread) + (! start thread) + (cons im thread)))) + +(df spawn/chan/catch (f) + (spawn/chan + (fun (c) + (try-catch + (f c) + (ex <throwable> + (send c `(error ,(! toString ex) + ,(class-name-sans-package ex) + ,(map (fun (e) (! to-string e)) + (array-to-list (! get-stack-trace ex)))))))))) + +;;;; Logging + +(define swank-log-port (current-error-port)) +(df log (fstr #!rest args) + (synchronized swank-log-port + (apply format swank-log-port fstr args) + (force-output swank-log-port)) + #!void) + +;;;; Random helpers + +(df 1+ (x) (+ x 1)) +(df 1- (x) (- x 1)) + +(df len (x => <int>) + (typecase x + (<list> (length x)) + (<str> (! length x)) + (<string> (string-length x)) + (<vector> (vector-length x)) + (<java.util.List> (! size x)) + (<object[]> (@ length x)))) + +;;(df put (tab key value) (hash-table-set! tab key value) tab) +;;(df get (tab key default) (hash-table-ref/default tab key default)) +;;(df del (tab key) (hash-table-delete! tab key) tab) +;;(df tab () (make-hash-table)) + +(df put (tab key value) (hashtable-set! tab key value) tab) +(df get (tab key default) (hashtable-ref tab key default)) +(df del (tab key) (hashtable-delete! tab key) tab) +(df tab () (make-eqv-hashtable)) + +(df equal (x y => <boolean>) (equal? x y)) + +(df current-thread (=> <thread>) (!s java.lang.Thread currentThread)) +(df current-time (=> <long>) (!s java.lang.System currentTimeMillis)) + +(df nul? (x) (== x #!null)) + +(df read-from-string (str) + (call-with-input-string str read)) + +;;(df print-to-string (obj) (call-with-output-string (fun (p) (write obj p)))) + +(df pprint-to-string (obj) + (let* ((w (<java.io.StringWriter>)) + (p (<out-port> w #t #f))) + (try-catch (print-object obj p) + (ex <throwable> + (format p "#<error while printing ~a ~a>" + ex (class-name-sans-package ex)))) + (! flush p) + (to-string (! getBuffer w)))) + +(df print-object (obj stream) + (typecase obj + #; + ((or (eql #!null) (eql #!eof) + <list> <number> <character> <string> <vector> <procedure> <boolean>) + (write obj stream)) + (#t + #;(print-unreadable-object obj stream) + (write obj stream) + ))) + +(df print-unreadable-object ((o <object>) stream) + (let* ((string (! to-string o)) + (class (! get-class o)) + (name (! get-name class)) + (simplename (! get-simple-name class))) + (cond ((! starts-with string "#<") + (format stream "~a" string)) + ((or (! starts-with string name) + (! starts-with string simplename)) + (format stream "#<~a>" string)) + (#t + (format stream "#<~a ~a>" name string))))) + +(define cat string-append) + +(df values-to-list (values) + (typecase values + (<gnu.mapping.Values> (array-to-list (! getValues values))) + (<object> (list values)))) + +;; (to-list (as-list (values 1 2 2))) + +(df array-to-list ((array <object[]>) => <list>) + (packing (pack) + (dotimes (i (@ length array)) + (pack (array i))))) + +(df lisp-bool (obj) + (cond ((== obj 'nil) #f) + ((== obj 't) #t) + (#t (error "Can't map lisp boolean" obj)))) + +(df path-sans-extension ((p path) => <string>) + (let ((ex (! get-extension p)) + (str (! to-string p))) + (to-string (cond ((not ex) str) + (#t (! substring str 0 (- (len str) (len ex) 1))))))) + +(df class-name-sans-package ((obj <object>)) + (cond ((nul? obj) "<#!null>") + (#t + (try-catch + (let* ((c (! get-class obj)) + (n (! get-simple-name c))) + (cond ((equal n "") (! get-name c)) + (#t n))) + (e <java.lang.Throwable> + (format "#<~a: ~a>" e (! get-message e))))))) + +(df list-env (#!optional (env :: <env> (!s <env> current))) + (let ((enum (! enumerateAllLocations env))) + (packing (pack) + (while (! hasMoreElements enum) + (pack (! nextLocation enum)))))) + +(df list-file (filename) + (with (port (call-with-input-file filename)) + (let* ((lang (!s gnu.expr.Language getDefaultLanguage)) + (messages (<gnu.text.SourceMessages>)) + (comp (! parse lang (as <in-port> port) messages 0))) + (! get-module comp)))) + +(df list-decls (file) + (let* ((module (as <gnu.expr.ModuleExp> (list-file file)))) + (do ((decl :: <gnu.expr.Declaration> + (! firstDecl module) (! nextDecl decl))) + ((nul? decl)) + (format #t "~a ~a:~d:~d\n" decl + (! getFileName decl) + (! getLineNumber decl) + (! getColumnNumber decl) + )))) + +(df %time (f) + (define-alias <mf> <java.lang.management.ManagementFactory>) + (define-alias <gc> <java.lang.management.GarbageCollectorMXBean>) + (let* ((gcs (!s <mf> getGarbageCollectorMXBeans)) + (mem (!s <mf> getMemoryMXBean)) + (jit (!s <mf> getCompilationMXBean)) + (oldjit (! getTotalCompilationTime jit)) + (oldgc (packing (pack) + (iter gcs (fun ((gc <gc>)) + (pack (cons gc + (list (! getCollectionCount gc) + (! getCollectionTime gc)))))))) + (heap (!! getUsed getHeapMemoryUsage mem)) + (nonheap (!! getUsed getNonHeapMemoryUsage mem)) + (start (!s java.lang.System nanoTime)) + (values (f)) + (end (!s java.lang.System nanoTime)) + (newheap (!! getUsed getHeapMemoryUsage mem)) + (newnonheap (!! getUsed getNonHeapMemoryUsage mem))) + (format #t "~&") + (let ((njit (! getTotalCompilationTime jit))) + (format #t "; JIT compilation: ~:d ms (~:d)\n" (- njit oldjit) njit)) + (iter gcs (fun ((gc <gc>)) + (mlet ((_ count time) (assoc gc oldgc)) + (format #t "; GC ~a: ~:d ms (~d)\n" + (! getName gc) + (- (! getCollectionTime gc) time) + (- (! getCollectionCount gc) count))))) + (format #t "; Heap: ~@:d (~:d)\n" (- newheap heap) newheap) + (format #t "; Non-Heap: ~@:d (~:d)\n" (- newnonheap nonheap) newnonheap) + (format #t "; Elapsed time: ~:d us\n" (/ (- end start) 1000)) + values)) + +(define-syntax time + (syntax-rules () + ((time form) + (%time (lambda () form))))) + +(df gc () + (let* ((mem (!s java.lang.management.ManagementFactory getMemoryMXBean)) + (oheap (!! getUsed getHeapMemoryUsage mem)) + (onheap (!! getUsed getNonHeapMemoryUsage mem)) + (_ (! gc mem)) + (heap (!! getUsed getHeapMemoryUsage mem)) + (nheap (!! getUsed getNonHeapMemoryUsage mem))) + (format #t "; heap: ~@:d (~:d) non-heap: ~@:d (~:d)\n" + (- heap oheap) heap (- onheap nheap) nheap))) + +(df room () + (let* ((pools (!s java.lang.management.ManagementFactory + getMemoryPoolMXBeans)) + (mem (!s java.lang.management.ManagementFactory getMemoryMXBean)) + (heap (!! getUsed getHeapMemoryUsage mem)) + (nheap (!! getUsed getNonHeapMemoryUsage mem))) + (iter pools (fun ((p <java.lang.management.MemoryPoolMXBean>)) + (format #t "~&; ~a~1,16t: ~10:d\n" + (! getName p) + (!! getUsed getUsage p)))) + (format #t "; Heap~1,16t: ~10:d\n" heap) + (format #t "; Non-Heap~1,16t: ~10:d\n" nheap))) + +;; (df javap (class #!key method signature) +;; (let* ((<is> <java.io.ByteArrayInputStream>) +;; (bytes +;; (typecase class +;; (<string> (read-bytes (<java.io.FileInputStream> (to-str class)))) +;; (<byte[]> class) +;; (<symbol> (read-class-file class)))) +;; (cdata (<sun.tools.javap.ClassData> (<is> bytes))) +;; (p (<sun.tools.javap.JavapPrinter> +;; (<is> bytes) +;; (current-output-port) +;; (<sun.tools.javap.JavapEnvironment>)))) +;; (cond (method +;; (dolist ((m <sun.tools.javap.MethodData>) +;; (array-to-list (! getMethods cdata))) +;; (when (and (equal (to-str method) (! getName m)) +;; (or (not signature) +;; (equal signature (! getInternalSig m)))) +;; (! printMethodSignature p m (! getAccess m)) +;; (! printExceptions p m) +;; (newline) +;; (! printVerboseHeader p m) +;; (! printcodeSequence p m)))) +;; (#t (p:print))) +;; (values))) + +(df read-bytes ((is <java.io.InputStream>) => <byte[]>) + (let ((os (<java.io.ByteArrayOutputStream>))) + (let loop () + (let ((c (! read is))) + (cond ((= c -1)) + (#t (! write os c) (loop))))) + (! to-byte-array os))) + +(df read-class-file ((name <symbol>) => <byte[]>) + (let ((f (cat (! replace (to-str name) (as <char> #\.) (as <char> #\/)) + ".class"))) + (mcase (find-file-in-path f (class-path)) + ('#f (ferror "Can't find classfile for ~s" name)) + ((:zip zipfile entry) + (let* ((z (<java.util.zip.ZipFile> (as <str> zipfile))) + (e (! getEntry z (as <str> entry)))) + (read-bytes (! getInputStream z e)))) + ((:file s) (read-bytes (<java.io.FileInputStream> (as <str> s))))))) + +(df all-instances ((vm <vm>) (classname <str>)) + (mappend (fun ((c <class-type>)) (to-list (! instances c (as long 9999)))) + (%all-subclasses vm classname))) + +(df %all-subclasses ((vm <vm>) (classname <str>)) + (mappend (fun ((c <class-type>)) (cons c (to-list (! subclasses c)))) + (to-list (! classes-by-name vm classname)))) + +(df with-output-to-string (thunk => <str>) + (call-with-output-string + (fun (s) (parameterize ((current-output-port s)) (thunk))))) + +(df find-if ((i <iterable>) test default) + (let ((iter (! iterator i)) + (found #f)) + (while (and (not found) (! has-next iter)) + (let ((e (! next iter))) + (when (test e) + (set found #t) + (set default e)))) + default)) + +(df filter ((i <iterable>) test => <list>) + (packing (pack) + (for ((e i)) + (when (test e) + (pack e))))) + +(df iter ((i <iterable>) f) + (for ((e i)) (f e))) + +(df mapi ((i <iterable>) f => <list>) + (packing (pack) (for ((e i)) (pack (f e))))) + +(df nth ((i <iterable>) (n <int>)) + (let ((iter (! iterator i))) + (dotimes (i n) + (! next iter)) + (! next iter))) + +(df 1st ((i <iterable>)) (!! next iterator i)) + +(df to-list ((i <iterable>) => <list>) + (packing (pack) (for ((e i)) (pack e)))) + +(df as-list ((o <java.lang.Object[]>) => <java.util.List>) + (!s java.util.Arrays asList o)) + +(df mappend (f list) + (apply append (map f list))) + +(df subseq (s from to) + (typecase s + (<list> (apply list (! sub-list s from to))) + (<vector> (apply vector (! sub-list s from to))) + (<str> (! substring s from to)) + (<byte[]> (let* ((len (as <int> (- to from))) + (t (<byte[]> #:length len))) + (!s java.lang.System arraycopy s from t 0 len) + t)))) + +(df to-string (obj => <string>) + (typecase obj + (<str> (<gnu.lists.FString> obj)) + ((satisfies string?) obj) + ((satisfies symbol?) (symbol->string obj)) + (<java.lang.StringBuffer> (<gnu.lists.FString> obj)) + (<java.lang.StringBuilder> (<gnu.lists.FString> obj)) + (#t (error "Not a string designator" obj + (class-name-sans-package obj))))) + +(df to-str (obj => <str>) + (cond ((instance? obj <str>) obj) + ((string? obj) (! toString obj)) + ((symbol? obj) (! getName (as <gnu.mapping.Symbol> obj))) + (#t (error "Not a string designator" obj + (class-name-sans-package obj))))) + +)) + +;; Local Variables: +;; mode: goo +;; compile-command: "\ +;; rm -rf classes && \ +;; JAVA_OPTS=-Xss2M kawa --r7rs -d classes -C swank-kawa.scm && \ +;; jar cf swank-kawa.jar -C classes ." +;; End: |