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