;;;;; -*- indent-tabs-mode: nil -*-
|
|
;;;
|
|
;;; swank-mezzano.lisp --- SLIME backend for Mezzano
|
|
;;;
|
|
;;; This code has been placed in the Public Domain. All warranties are
|
|
;;; disclaimed.
|
|
;;;
|
|
|
|
;;; Administrivia
|
|
|
|
(defpackage swank/mezzano
|
|
(:use cl swank/backend))
|
|
|
|
(in-package swank/mezzano)
|
|
|
|
;;; swank-mop
|
|
|
|
(import-swank-mop-symbols :mezzano.clos '(:class-default-initargs
|
|
:class-direct-default-initargs
|
|
:specializer-direct-methods
|
|
:generic-function-declarations))
|
|
|
|
(defun swank-mop:specializer-direct-methods (obj)
|
|
(declare (ignore obj))
|
|
'())
|
|
|
|
(defun swank-mop:generic-function-declarations (gf)
|
|
(declare (ignore gf))
|
|
'())
|
|
|
|
(defimplementation gray-package-name ()
|
|
"MEZZANO.GRAY")
|
|
|
|
;;;; TCP server
|
|
|
|
(defclass listen-socket ()
|
|
((%listener :initarg :listener)))
|
|
|
|
(defimplementation create-socket (host port &key backlog)
|
|
(make-instance 'listen-socket
|
|
:listener (mezzano.network.tcp:tcp-listen
|
|
host
|
|
port
|
|
:backlog (or backlog 10))))
|
|
|
|
(defimplementation local-port (socket)
|
|
(mezzano.network.tcp:tcp-listener-local-port (slot-value socket '%listener)))
|
|
|
|
(defimplementation close-socket (socket)
|
|
(mezzano.network.tcp:close-tcp-listener (slot-value socket '%listener)))
|
|
|
|
(defimplementation accept-connection (socket &key external-format
|
|
buffering timeout)
|
|
(declare (ignore external-format buffering timeout))
|
|
(loop
|
|
(let ((value (mezzano.network.tcp:tcp-accept (slot-value socket '%listener)
|
|
:wait-p nil)))
|
|
(if value
|
|
(return value)
|
|
;; Poke standard-input every now and then to keep the console alive.
|
|
(progn (listen)
|
|
(sleep 0.05))))))
|
|
|
|
(defimplementation preferred-communication-style ()
|
|
:spawn)
|
|
|
|
;;;; Unix signals
|
|
;;;; ????
|
|
|
|
(defimplementation getpid ()
|
|
0)
|
|
|
|
;;;; Compilation
|
|
|
|
(defun signal-compiler-condition (condition severity)
|
|
(signal 'compiler-condition
|
|
:original-condition condition
|
|
:severity severity
|
|
:message (format nil "~A" condition)
|
|
:location nil))
|
|
|
|
(defimplementation call-with-compilation-hooks (func)
|
|
(handler-bind
|
|
((error
|
|
(lambda (c)
|
|
(signal-compiler-condition c :error)))
|
|
(warning
|
|
(lambda (c)
|
|
(signal-compiler-condition c :warning)))
|
|
(style-warning
|
|
(lambda (c)
|
|
(signal-compiler-condition c :style-warning))))
|
|
(funcall func)))
|
|
|
|
(defimplementation swank-compile-string (string &key buffer position filename
|
|
policy)
|
|
(declare (ignore buffer policy))
|
|
(let* ((*load-pathname* (ignore-errors (pathname filename)))
|
|
(*load-truename* (when *load-pathname*
|
|
(ignore-errors (truename *load-pathname*))))
|
|
(sys.int::*top-level-form-number* `(:position ,position)))
|
|
(with-compilation-hooks ()
|
|
(eval (read-from-string (concatenate 'string "(progn " string " )")))))
|
|
t)
|
|
|
|
(defimplementation swank-compile-file (input-file output-file load-p
|
|
external-format
|
|
&key policy)
|
|
(with-compilation-hooks ()
|
|
(multiple-value-prog1
|
|
(compile-file input-file
|
|
:output-file output-file
|
|
:external-format external-format)
|
|
(when load-p
|
|
(load output-file)))))
|
|
|
|
(defimplementation find-external-format (coding-system)
|
|
(if (or (equal coding-system "utf-8")
|
|
(equal coding-system "utf-8-unix"))
|
|
:default
|
|
nil))
|
|
|
|
;;;; Debugging
|
|
|
|
;; Definitely don't allow this.
|
|
(defimplementation install-debugger-globally (function)
|
|
(declare (ignore function))
|
|
nil)
|
|
|
|
(defvar *current-backtrace*)
|
|
|
|
(defimplementation call-with-debugging-environment (debugger-loop-fn)
|
|
(let ((*current-backtrace* '()))
|
|
(let ((prev-fp nil))
|
|
(sys.int::map-backtrace
|
|
(lambda (i fp)
|
|
(push (list (1- i) fp prev-fp) *current-backtrace*)
|
|
(setf prev-fp fp))))
|
|
(setf *current-backtrace* (reverse *current-backtrace*))
|
|
;; Drop the topmost frame, which is finished call to MAP-BACKTRACE.
|
|
(pop *current-backtrace*)
|
|
;; And the next one for good measure.
|
|
(pop *current-backtrace*)
|
|
(funcall debugger-loop-fn)))
|
|
|
|
(defimplementation compute-backtrace (start end)
|
|
(subseq *current-backtrace* start end))
|
|
|
|
(defimplementation print-frame (frame stream)
|
|
(format stream "~S" (sys.int::function-from-frame frame)))
|
|
|
|
(defimplementation frame-source-location (frame-number)
|
|
(let* ((frame (nth frame-number *current-backtrace*))
|
|
(fn (sys.int::function-from-frame frame)))
|
|
(function-location fn)))
|
|
|
|
(defimplementation frame-locals (frame-number)
|
|
(loop
|
|
with frame = (nth frame-number *current-backtrace*)
|
|
for (name id location repr) in (sys.int::frame-locals frame)
|
|
collect (list :name name
|
|
:id id
|
|
:value (sys.int::read-frame-slot frame location repr))))
|
|
|
|
(defimplementation frame-var-value (frame-number var-id)
|
|
(let* ((frame (nth frame-number *current-backtrace*))
|
|
(locals (sys.int::frame-locals frame))
|
|
(info (nth var-id locals)))
|
|
(if info
|
|
(destructuring-bind (name id location repr)
|
|
info
|
|
(declare (ignore id))
|
|
(values (sys.int::read-frame-slot frame location repr) name))
|
|
(error "Invalid variable id ~D for frame number ~D."
|
|
var-id frame-number))))
|
|
|
|
;;;; Definition finding
|
|
|
|
(defun top-level-form-position (pathname tlf)
|
|
(ignore-errors
|
|
(with-open-file (s pathname)
|
|
(loop
|
|
repeat tlf
|
|
do (with-standard-io-syntax
|
|
(let ((*read-suppress* t)
|
|
(*read-eval* nil))
|
|
(read s nil))))
|
|
(let ((default (make-pathname :host (pathname-host s))))
|
|
(make-location `(:file ,(enough-namestring s default))
|
|
`(:position ,(1+ (file-position s))))))))
|
|
|
|
(defun function-location (function)
|
|
"Return a location object for FUNCTION."
|
|
(let* ((info (sys.int::function-debug-info function))
|
|
(pathname (sys.int::debug-info-source-pathname info))
|
|
(tlf (sys.int::debug-info-source-top-level-form-number info)))
|
|
(cond ((and (consp tlf)
|
|
(eql (first tlf) :position))
|
|
(let ((default (make-pathname :host (pathname-host pathname))))
|
|
(make-location `(:file ,(enough-namestring pathname default))
|
|
`(:position ,(second tlf)))))
|
|
(t
|
|
(top-level-form-position pathname tlf)))))
|
|
|
|
(defun method-definition-name (name method)
|
|
`(defmethod ,name
|
|
,@(mezzano.clos:method-qualifiers method)
|
|
,(mapcar (lambda (x)
|
|
(typecase x
|
|
(mezzano.clos:class
|
|
(mezzano.clos:class-name x))
|
|
(mezzano.clos:eql-specializer
|
|
`(eql ,(mezzano.clos:eql-specializer-object x)))
|
|
(t x)))
|
|
(mezzano.clos:method-specializers method))))
|
|
|
|
(defimplementation find-definitions (name)
|
|
(let ((result '()))
|
|
(labels
|
|
((frob-fn (dspec fn)
|
|
(let ((loc (function-location fn)))
|
|
(when loc
|
|
(push (list dspec loc) result))))
|
|
(try-fn (name)
|
|
(when (valid-function-name-p name)
|
|
(when (and (fboundp name)
|
|
(not (and (symbolp name)
|
|
(or (special-operator-p name)
|
|
(macro-function name)))))
|
|
(let ((fn (fdefinition name)))
|
|
(cond ((typep fn 'mezzano.clos:standard-generic-function)
|
|
(dolist (m (mezzano.clos:generic-function-methods fn))
|
|
(frob-fn (method-definition-name name m)
|
|
(mezzano.clos:method-function m))))
|
|
(t
|
|
(frob-fn `(defun ,name) fn)))))
|
|
(when (compiler-macro-function name)
|
|
(frob-fn `(define-compiler-macro ,name)
|
|
(compiler-macro-function name))))))
|
|
(try-fn name)
|
|
(try-fn `(setf name))
|
|
(try-fn `(sys.int::cas name))
|
|
(when (and (symbolp name)
|
|
(get name 'sys.int::setf-expander))
|
|
(frob-fn `(define-setf-expander ,name)
|
|
(get name 'sys.int::setf-expander)))
|
|
(when (and (symbolp name)
|
|
(macro-function name))
|
|
(frob-fn `(defmacro ,name)
|
|
(macro-function name))))
|
|
result))
|
|
|
|
;;;; XREF
|
|
;;; Simpler variants.
|
|
|
|
(defun find-all-frefs ()
|
|
(let ((frefs (make-array 500 :adjustable t :fill-pointer 0))
|
|
(keep-going t))
|
|
(loop
|
|
(when (not keep-going)
|
|
(return))
|
|
(adjust-array frefs (* (array-dimension frefs 0) 2))
|
|
(setf keep-going nil
|
|
(fill-pointer frefs) 0)
|
|
;; Walk the wired area looking for FREFs.
|
|
(sys.int::walk-area
|
|
:wired
|
|
(lambda (object address size)
|
|
(when (sys.int::function-reference-p object)
|
|
(when (not (vector-push object frefs))
|
|
(setf keep-going t))))))
|
|
(remove-duplicates (coerce frefs 'list))))
|
|
|
|
(defimplementation list-callers (function-name)
|
|
(let ((fref-for-fn (sys.int::function-reference function-name))
|
|
(callers '()))
|
|
(loop
|
|
for fref in (find-all-frefs)
|
|
for fn = (sys.int::function-reference-function fref)
|
|
for name = (sys.int::function-reference-name fref)
|
|
when fn
|
|
do
|
|
(cond ((typep fn 'standard-generic-function)
|
|
(dolist (m (mezzano.clos:generic-function-methods fn))
|
|
(let* ((mf (mezzano.clos:method-function m))
|
|
(mf-frefs (get-all-frefs-in-function mf)))
|
|
(when (member fref-for-fn mf-frefs)
|
|
(push `((defmethod ,name
|
|
,@(mezzano.clos:method-qualifiers m)
|
|
,(mapcar #'specializer-name
|
|
(mezzano.clos:method-specializers m)))
|
|
,(function-location mf))
|
|
callers)))))
|
|
((member fref-for-fn
|
|
(get-all-frefs-in-function fn))
|
|
(push `((defun ,name) ,(function-location fn)) callers))))
|
|
callers))
|
|
|
|
(defun specializer-name (specializer)
|
|
(if (typep specializer 'standard-class)
|
|
(mezzano.clos:class-name specializer)
|
|
specializer))
|
|
|
|
(defun get-all-frefs-in-function (function)
|
|
(when (sys.int::funcallable-std-instance-p function)
|
|
(setf function (sys.int::funcallable-std-instance-function function)))
|
|
(when (sys.int::closure-p function)
|
|
(setf function (sys.int::%closure-function function)))
|
|
(loop
|
|
for i below (sys.int::function-pool-size function)
|
|
for entry = (sys.int::function-pool-object function i)
|
|
when (sys.int::function-reference-p entry)
|
|
collect entry
|
|
when (compiled-function-p entry) ; closures
|
|
append (get-all-frefs-in-function entry)))
|
|
|
|
(defimplementation list-callees (function-name)
|
|
(let* ((fn (fdefinition function-name))
|
|
;; Grovel around in the function's constant pool looking for
|
|
;; function-references. These may be for #', but they're
|
|
;; probably going to be for normal calls.
|
|
;; TODO: This doesn't work well on interpreted functions or
|
|
;; funcallable instances.
|
|
(callees (remove-duplicates (get-all-frefs-in-function fn))))
|
|
(loop
|
|
for fref in callees
|
|
for name = (sys.int::function-reference-name fref)
|
|
for fn = (sys.int::function-reference-function fref)
|
|
when fn
|
|
collect `((defun ,name) ,(function-location fn)))))
|
|
|
|
;;;; Documentation
|
|
|
|
(defimplementation arglist (name)
|
|
(let ((macro (when (symbolp name)
|
|
(macro-function name)))
|
|
(fn (if (functionp name)
|
|
name
|
|
(ignore-errors (fdefinition name)))))
|
|
(cond
|
|
(macro
|
|
(get name 'sys.int::macro-lambda-list))
|
|
(fn
|
|
(cond
|
|
((typep fn 'mezzano.clos:standard-generic-function)
|
|
(mezzano.clos:generic-function-lambda-list fn))
|
|
(t
|
|
(function-lambda-list fn))))
|
|
(t :not-available))))
|
|
|
|
(defun function-lambda-list (function)
|
|
(sys.int::debug-info-lambda-list
|
|
(sys.int::function-debug-info function)))
|
|
|
|
(defimplementation type-specifier-p (symbol)
|
|
(cond
|
|
((or (get symbol 'sys.int::type-expander)
|
|
(get symbol 'sys.int::compound-type)
|
|
(get symbol 'sys.int::type-symbol))
|
|
t)
|
|
(t :not-available)))
|
|
|
|
(defimplementation function-name (function)
|
|
(sys.int::function-name function))
|
|
|
|
(defimplementation valid-function-name-p (form)
|
|
"Is FORM syntactically valid to name a function?
|
|
If true, FBOUNDP should not signal a type-error for FORM."
|
|
(flet ((length=2 (list)
|
|
(and (not (null (cdr list))) (null (cddr list)))))
|
|
(or (symbolp form)
|
|
(and (consp form) (length=2 form)
|
|
(or (eq (first form) 'setf)
|
|
(eq (first form) 'sys.int::cas))
|
|
(symbolp (second form))))))
|
|
|
|
(defimplementation describe-symbol-for-emacs (symbol)
|
|
(let ((result '()))
|
|
(when (boundp symbol)
|
|
(setf (getf result :variable) nil))
|
|
(when (and (fboundp symbol)
|
|
(not (macro-function symbol)))
|
|
(setf (getf result :function)
|
|
(function-docstring symbol)))
|
|
(when (fboundp `(setf ,symbol))
|
|
(setf (getf result :setf)
|
|
(function-docstring `(setf ,symbol))))
|
|
(when (get symbol 'sys.int::setf-expander)
|
|
(setf (getf result :setf) nil))
|
|
(when (special-operator-p symbol)
|
|
(setf (getf result :special-operator) nil))
|
|
(when (macro-function symbol)
|
|
(setf (getf result :macro) nil))
|
|
(when (compiler-macro-function symbol)
|
|
(setf (getf result :compiler-macro) nil))
|
|
(when (type-specifier-p symbol)
|
|
(setf (getf result :type) nil))
|
|
(when (find-class symbol nil)
|
|
(setf (getf result :class) nil))
|
|
result))
|
|
|
|
(defun function-docstring (function-name)
|
|
(let* ((definition (fdefinition function-name))
|
|
(debug-info (sys.int::function-debug-info definition)))
|
|
(sys.int::debug-info-docstring debug-info)))
|
|
|
|
;;;; Multithreading
|
|
|
|
;; FIXME: This should be a weak table.
|
|
(defvar *thread-ids-for-emacs* (make-hash-table))
|
|
(defvar *next-thread-id-for-emacs* 0)
|
|
(defvar *thread-id-for-emacs-lock* (mezzano.supervisor:make-mutex
|
|
"SWANK thread ID table"))
|
|
|
|
(defimplementation spawn (fn &key name)
|
|
(mezzano.supervisor:make-thread fn :name name))
|
|
|
|
(defimplementation thread-id (thread)
|
|
(mezzano.supervisor:with-mutex (*thread-id-for-emacs-lock*)
|
|
(let ((id (gethash thread *thread-ids-for-emacs*)))
|
|
(when (null id)
|
|
(setf id (incf *next-thread-id-for-emacs*)
|
|
(gethash thread *thread-ids-for-emacs*) id
|
|
(gethash id *thread-ids-for-emacs*) thread))
|
|
id)))
|
|
|
|
(defimplementation find-thread (id)
|
|
(mezzano.supervisor:with-mutex (*thread-id-for-emacs-lock*)
|
|
(gethash id *thread-ids-for-emacs*)))
|
|
|
|
(defimplementation thread-name (thread)
|
|
(mezzano.supervisor:thread-name thread))
|
|
|
|
(defimplementation thread-status (thread)
|
|
(format nil "~:(~A~)" (mezzano.supervisor:thread-state thread)))
|
|
|
|
(defimplementation current-thread ()
|
|
(mezzano.supervisor:current-thread))
|
|
|
|
(defimplementation all-threads ()
|
|
(mezzano.supervisor:all-threads))
|
|
|
|
(defimplementation thread-alive-p (thread)
|
|
(not (eql (mezzano.supervisor:thread-state thread) :dead)))
|
|
|
|
(defimplementation interrupt-thread (thread fn)
|
|
(mezzano.supervisor:establish-thread-foothold thread fn))
|
|
|
|
(defimplementation kill-thread (thread)
|
|
;; Documentation says not to execute unwind-protected sections, but there's
|
|
;; no way to do that.
|
|
;; And killing threads at arbitrary points without unwinding them is a good
|
|
;; way to hose the system.
|
|
(mezzano.supervisor:terminate-thread thread))
|
|
|
|
(defvar *mailbox-lock* (mezzano.supervisor:make-mutex "mailbox lock"))
|
|
(defvar *mailboxes* (list))
|
|
|
|
(defstruct (mailbox (:conc-name mailbox.))
|
|
thread
|
|
(mutex (mezzano.supervisor:make-mutex))
|
|
(queue '() :type list))
|
|
|
|
(defun mailbox (thread)
|
|
"Return THREAD's mailbox."
|
|
;; Use weak pointers to avoid holding on to dead threads forever.
|
|
(mezzano.supervisor:with-mutex (*mailbox-lock*)
|
|
;; Flush forgotten threads.
|
|
(setf *mailboxes*
|
|
(remove-if-not #'sys.int::weak-pointer-value *mailboxes*))
|
|
(loop
|
|
for entry in *mailboxes*
|
|
do
|
|
(multiple-value-bind (key value livep)
|
|
(sys.int::weak-pointer-pair entry)
|
|
(when (eql key thread)
|
|
(return value)))
|
|
finally
|
|
(let ((mb (make-mailbox :thread thread)))
|
|
(push (sys.int::make-weak-pointer thread mb) *mailboxes*)
|
|
(return mb)))))
|
|
|
|
(defimplementation send (thread message)
|
|
(let* ((mbox (mailbox thread))
|
|
(mutex (mailbox.mutex mbox)))
|
|
(mezzano.supervisor:with-mutex (mutex)
|
|
(setf (mailbox.queue mbox)
|
|
(nconc (mailbox.queue mbox) (list message))))))
|
|
|
|
(defvar *receive-if-sleep-time* 0.02)
|
|
|
|
(defimplementation receive-if (test &optional timeout)
|
|
(let* ((mbox (mailbox (current-thread)))
|
|
(mutex (mailbox.mutex mbox)))
|
|
(assert (or (not timeout) (eq timeout t)))
|
|
(loop
|
|
(check-slime-interrupts)
|
|
(mezzano.supervisor:with-mutex (mutex)
|
|
(let* ((q (mailbox.queue mbox))
|
|
(tail (member-if test q)))
|
|
(when tail
|
|
(setf (mailbox.queue mbox) (nconc (ldiff q tail) (cdr tail)))
|
|
(return (car tail))))
|
|
(when (eq timeout t) (return (values nil t))))
|
|
(sleep *receive-if-sleep-time*))))
|
|
|
|
(defvar *registered-threads* (make-hash-table))
|
|
(defvar *registered-threads-lock*
|
|
(mezzano.supervisor:make-mutex "registered threads lock"))
|
|
|
|
(defimplementation register-thread (name thread)
|
|
(declare (type symbol name))
|
|
(mezzano.supervisor:with-mutex (*registered-threads-lock*)
|
|
(etypecase thread
|
|
(null
|
|
(remhash name *registered-threads*))
|
|
(mezzano.supervisor:thread
|
|
(setf (gethash name *registered-threads*) thread))))
|
|
nil)
|
|
|
|
(defimplementation find-registered (name)
|
|
(mezzano.supervisor:with-mutex (*registered-threads-lock*)
|
|
(values (gethash name *registered-threads*))))
|
|
|
|
(defimplementation wait-for-input (streams &optional timeout)
|
|
(loop
|
|
(let ((ready '()))
|
|
(dolist (s streams)
|
|
(when (or (listen s)
|
|
(and (typep s 'mezzano.network.tcp::tcp-stream)
|
|
(mezzano.network.tcp::tcp-connection-closed-p s)))
|
|
(push s ready)))
|
|
(when ready
|
|
(return ready))
|
|
(when (check-slime-interrupts)
|
|
(return :interrupt))
|
|
(when timeout
|
|
(return '()))
|
|
(sleep 1)
|
|
(when (numberp timeout)
|
|
(decf timeout 1)
|
|
(when (not (plusp timeout))
|
|
(return '()))))))
|
|
|
|
;;;; Locks
|
|
|
|
(defstruct recursive-lock
|
|
mutex
|
|
(depth 0))
|
|
|
|
(defimplementation make-lock (&key name)
|
|
(make-recursive-lock
|
|
:mutex (mezzano.supervisor:make-mutex name)))
|
|
|
|
(defimplementation call-with-lock-held (lock function)
|
|
(cond ((mezzano.supervisor:mutex-held-p
|
|
(recursive-lock-mutex lock))
|
|
(unwind-protect
|
|
(progn (incf (recursive-lock-depth lock))
|
|
(funcall function))
|
|
(decf (recursive-lock-depth lock))))
|
|
(t
|
|
(mezzano.supervisor:with-mutex ((recursive-lock-mutex lock))
|
|
(multiple-value-prog1
|
|
(funcall function)
|
|
(assert (eql (recursive-lock-depth lock) 0)))))))
|
|
|
|
;;;; Character names
|
|
|
|
(defimplementation character-completion-set (prefix matchp)
|
|
;; TODO: Unicode characters too.
|
|
(loop
|
|
for names in sys.int::*char-name-alist*
|
|
append
|
|
(loop
|
|
for name in (rest names)
|
|
when (funcall matchp prefix name)
|
|
collect name)))
|
|
|
|
;;;; Inspector
|
|
|
|
(defmethod emacs-inspect ((o function))
|
|
(case (sys.int::%object-tag o)
|
|
(#.sys.int::+object-tag-function+
|
|
(label-value-line*
|
|
(:name (sys.int::function-name o))
|
|
(:arglist (arglist o))
|
|
(:debug-info (sys.int::function-debug-info o))))
|
|
(#.sys.int::+object-tag-closure+
|
|
(append
|
|
(label-value-line :function (sys.int::%closure-function o))
|
|
`("Closed over values:" (:newline))
|
|
(loop
|
|
for i below (sys.int::%closure-length o)
|
|
append (label-value-line i (sys.int::%closure-value o i)))))
|
|
(t
|
|
(call-next-method))))
|
|
|
|
(defmethod emacs-inspect ((o sys.int::weak-pointer))
|
|
(label-value-line*
|
|
(:key (sys.int::weak-pointer-key o))
|
|
(:value (sys.int::weak-pointer-value o))))
|
|
|
|
(defmethod emacs-inspect ((o sys.int::function-reference))
|
|
(label-value-line*
|
|
(:name (sys.int::function-reference-name o))
|
|
(:function (sys.int::function-reference-function o))))
|
|
|
|
(defmethod emacs-inspect ((object structure-object))
|
|
(let ((class (class-of object)))
|
|
`("Class: " (:value ,class) (:newline)
|
|
,@(swank::all-slots-for-inspector object))))
|
|
|
|
(in-package :swank)
|
|
|
|
(defmethod all-slots-for-inspector ((object structure-object))
|
|
(let* ((class (class-of object))
|
|
(direct-slots (swank-mop:class-direct-slots class))
|
|
(effective-slots (swank-mop:class-slots class))
|
|
(longest-slot-name-length
|
|
(loop for slot :in effective-slots
|
|
maximize (length (symbol-name
|
|
(swank-mop:slot-definition-name slot)))))
|
|
(checklist
|
|
(reinitialize-checklist
|
|
(ensure-istate-metadata object :checklist
|
|
(make-checklist (length effective-slots)))))
|
|
(grouping-kind
|
|
;; We box the value so we can re-set it.
|
|
(ensure-istate-metadata object :grouping-kind
|
|
(box *inspector-slots-default-grouping*)))
|
|
(sort-order
|
|
(ensure-istate-metadata object :sort-order
|
|
(box *inspector-slots-default-order*)))
|
|
(sort-predicate (ecase (ref sort-order)
|
|
(:alphabetically #'string<)
|
|
(:unsorted (constantly nil))))
|
|
(sorted-slots (sort (copy-seq effective-slots)
|
|
sort-predicate
|
|
:key #'swank-mop:slot-definition-name))
|
|
(effective-slots
|
|
(ecase (ref grouping-kind)
|
|
(:all sorted-slots)
|
|
(:inheritance (stable-sort-by-inheritance sorted-slots
|
|
class sort-predicate)))))
|
|
`("--------------------"
|
|
(:newline)
|
|
" Group slots by inheritance "
|
|
(:action ,(ecase (ref grouping-kind)
|
|
(:all "[ ]")
|
|
(:inheritance "[X]"))
|
|
,(lambda ()
|
|
;; We have to do this as the order of slots will
|
|
;; be sorted differently.
|
|
(fill (checklist.buttons checklist) nil)
|
|
(setf (ref grouping-kind)
|
|
(ecase (ref grouping-kind)
|
|
(:all :inheritance)
|
|
(:inheritance :all))))
|
|
:refreshp t)
|
|
(:newline)
|
|
" Sort slots alphabetically "
|
|
(:action ,(ecase (ref sort-order)
|
|
(:unsorted "[ ]")
|
|
(:alphabetically "[X]"))
|
|
,(lambda ()
|
|
(fill (checklist.buttons checklist) nil)
|
|
(setf (ref sort-order)
|
|
(ecase (ref sort-order)
|
|
(:unsorted :alphabetically)
|
|
(:alphabetically :unsorted))))
|
|
:refreshp t)
|
|
(:newline)
|
|
,@ (case (ref grouping-kind)
|
|
(:all
|
|
`((:newline)
|
|
"All Slots:"
|
|
(:newline)
|
|
,@(make-slot-listing checklist object class
|
|
effective-slots direct-slots
|
|
longest-slot-name-length)))
|
|
(:inheritance
|
|
(list-all-slots-by-inheritance checklist object class
|
|
effective-slots direct-slots
|
|
longest-slot-name-length)))
|
|
(:newline)
|
|
(:action "[set value]"
|
|
,(lambda ()
|
|
(do-checklist (idx checklist)
|
|
(query-and-set-slot class object
|
|
(nth idx effective-slots))))
|
|
:refreshp t)
|
|
" "
|
|
(:action "[make unbound]"
|
|
,(lambda ()
|
|
(do-checklist (idx checklist)
|
|
(swank-mop:slot-makunbound-using-class
|
|
class object (nth idx effective-slots))))
|
|
:refreshp t)
|
|
(:newline))))
|