|
;;; -*- indent-tabs-mode: nil; outline-regexp: ";;;;;*" -*-
|
|
;;;
|
|
;;; slime-backend.lisp --- SLIME backend interface.
|
|
;;;
|
|
;;; Created by James Bielman in 2003. Released into the public domain.
|
|
;;;
|
|
;;;; Frontmatter
|
|
;;;
|
|
;;; This file defines the functions that must be implemented
|
|
;;; separately for each Lisp. Each is declared as a generic function
|
|
;;; for which swank-<implementation>.lisp provides methods.
|
|
|
|
(in-package swank/backend)
|
|
|
|
|
|
;;;; Metacode
|
|
|
|
(defparameter *debug-swank-backend* nil
|
|
"If this is true, backends should not catch errors but enter the
|
|
debugger where appropriate. Also, they should not perform backtrace
|
|
magic but really show every frame including SWANK related ones.")
|
|
|
|
(defparameter *interface-functions* '()
|
|
"The names of all interface functions.")
|
|
|
|
(defparameter *unimplemented-interfaces* '()
|
|
"List of interface functions that are not implemented.
|
|
DEFINTERFACE adds to this list and DEFIMPLEMENTATION removes.")
|
|
|
|
(defvar *log-output* nil) ; should be nil for image dumpers
|
|
|
|
(defmacro definterface (name args documentation &rest default-body)
|
|
"Define an interface function for the backend to implement.
|
|
A function is defined with NAME, ARGS, and DOCUMENTATION. This
|
|
function first looks for a function to call in NAME's property list
|
|
that is indicated by 'IMPLEMENTATION; failing that, it looks for a
|
|
function indicated by 'DEFAULT. If neither is present, an error is
|
|
signaled.
|
|
|
|
If a DEFAULT-BODY is supplied, then a function with the same body and
|
|
ARGS will be added to NAME's property list as the property indicated
|
|
by 'DEFAULT.
|
|
|
|
Backends implement these functions using DEFIMPLEMENTATION."
|
|
(check-type documentation string "a documentation string")
|
|
(assert (every #'symbolp args) ()
|
|
"Complex lambda-list not supported: ~S ~S" name args)
|
|
(labels ((gen-default-impl ()
|
|
`(setf (get ',name 'default) (lambda ,args ,@default-body)))
|
|
(args-as-list (args)
|
|
(destructuring-bind (req opt key rest) (parse-lambda-list args)
|
|
`(,@req ,@opt
|
|
,@(loop for k in key append `(,(kw k) ,k))
|
|
,@(or rest '(())))))
|
|
(parse-lambda-list (args)
|
|
(parse args '(&optional &key &rest)
|
|
(make-array 4 :initial-element nil)))
|
|
(parse (args keywords vars)
|
|
(cond ((null args)
|
|
(reverse (map 'list #'reverse vars)))
|
|
((member (car args) keywords)
|
|
(parse (cdr args) (cdr (member (car args) keywords)) vars))
|
|
(t (push (car args) (aref vars (length keywords)))
|
|
(parse (cdr args) keywords vars))))
|
|
(kw (s) (intern (string s) :keyword)))
|
|
`(progn
|
|
(defun ,name ,args
|
|
,documentation
|
|
(let ((f (or (get ',name 'implementation)
|
|
(get ',name 'default))))
|
|
(cond (f (apply f ,@(args-as-list args)))
|
|
(t (error "~S not implemented" ',name)))))
|
|
(pushnew ',name *interface-functions*)
|
|
,(if (null default-body)
|
|
`(pushnew ',name *unimplemented-interfaces*)
|
|
(gen-default-impl))
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(export ',name :swank/backend))
|
|
',name)))
|
|
|
|
(defmacro defimplementation (name args &body body)
|
|
(assert (every #'symbolp args) ()
|
|
"Complex lambda-list not supported: ~S ~S" name args)
|
|
`(progn
|
|
(setf (get ',name 'implementation)
|
|
;; For implicit BLOCK. FLET because of interplay w/ decls.
|
|
(flet ((,name ,args ,@body)) #',name))
|
|
(if (member ',name *interface-functions*)
|
|
(setq *unimplemented-interfaces*
|
|
(remove ',name *unimplemented-interfaces*))
|
|
(warn "DEFIMPLEMENTATION of undefined interface (~S)" ',name))
|
|
',name))
|
|
|
|
(defun warn-unimplemented-interfaces ()
|
|
"Warn the user about unimplemented backend features.
|
|
The portable code calls this function at startup."
|
|
(let ((*print-pretty* t))
|
|
(warn "These Swank interfaces are unimplemented:~% ~:<~{~A~^ ~:_~}~:>"
|
|
(list (sort (copy-list *unimplemented-interfaces*) #'string<)))))
|
|
|
|
(defun import-to-swank-mop (symbol-list)
|
|
(dolist (sym symbol-list)
|
|
(let* ((swank-mop-sym (find-symbol (symbol-name sym) :swank-mop)))
|
|
(when swank-mop-sym
|
|
(unintern swank-mop-sym :swank-mop))
|
|
(import sym :swank-mop)
|
|
(export sym :swank-mop))))
|
|
|
|
(defun import-swank-mop-symbols (package except)
|
|
"Import the mop symbols from PACKAGE to SWANK-MOP.
|
|
EXCEPT is a list of symbol names which should be ignored."
|
|
(do-symbols (s :swank-mop)
|
|
(unless (member s except :test #'string=)
|
|
(let ((real-symbol (find-symbol (string s) package)))
|
|
(assert real-symbol () "Symbol ~A not found in package ~A" s package)
|
|
(unintern s :swank-mop)
|
|
(import real-symbol :swank-mop)
|
|
(export real-symbol :swank-mop)))))
|
|
|
|
(definterface gray-package-name ()
|
|
"Return a package-name that contains the Gray stream symbols.
|
|
This will be used like so:
|
|
(defpackage foo
|
|
(:import-from #.(gray-package-name) . #.*gray-stream-symbols*)")
|
|
|
|
|
|
;;;; Utilities
|
|
|
|
(defmacro with-struct ((conc-name &rest names) obj &body body)
|
|
"Like with-slots but works only for structs."
|
|
(check-type conc-name symbol)
|
|
(flet ((reader (slot)
|
|
(intern (concatenate 'string
|
|
(symbol-name conc-name)
|
|
(symbol-name slot))
|
|
(symbol-package conc-name))))
|
|
(let ((tmp (gensym "OO-")))
|
|
` (let ((,tmp ,obj))
|
|
(symbol-macrolet
|
|
,(loop for name in names collect
|
|
(typecase name
|
|
(symbol `(,name (,(reader name) ,tmp)))
|
|
(cons `(,(first name) (,(reader (second name)) ,tmp)))
|
|
(t (error "Malformed syntax in WITH-STRUCT: ~A" name))))
|
|
,@body)))))
|
|
|
|
(defmacro when-let ((var value) &body body)
|
|
`(let ((,var ,value))
|
|
(when ,var ,@body)))
|
|
|
|
(defun boolean-to-feature-expression (value)
|
|
"Converts a boolean VALUE to a form suitable for testing with #+."
|
|
(if value
|
|
'(:and)
|
|
'(:or)))
|
|
|
|
(defun with-symbol (name package)
|
|
"Check if a symbol with a given NAME exists in PACKAGE and returns a
|
|
form suitable for testing with #+."
|
|
(boolean-to-feature-expression
|
|
(and (find-package package)
|
|
(find-symbol (string name) package))))
|
|
|
|
(defun choose-symbol (package name alt-package alt-name)
|
|
"If symbol package:name exists return that symbol, otherwise alt-package:alt-name.
|
|
Suitable for use with #."
|
|
(or (and (find-package package)
|
|
(find-symbol (string name) package))
|
|
(find-symbol (string alt-name) alt-package)))
|
|
|
|
|
|
;;;; UFT8
|
|
|
|
(deftype octet () '(unsigned-byte 8))
|
|
(deftype octets () '(simple-array octet (*)))
|
|
|
|
;; Helper function. Decode the next N bytes starting from INDEX.
|
|
;; Return the decoded char and the new index.
|
|
(defun utf8-decode-aux (buffer index limit byte0 n)
|
|
(declare (type octets buffer) (fixnum index limit byte0 n))
|
|
(if (< (- limit index) n)
|
|
(values nil index)
|
|
(do ((i 0 (1+ i))
|
|
(code byte0 (let ((byte (aref buffer (+ index i))))
|
|
(cond ((= (ldb (byte 2 6) byte) #b10)
|
|
(+ (ash code 6) (ldb (byte 6 0) byte)))
|
|
(t
|
|
(error "Invalid encoding"))))))
|
|
((= i n)
|
|
(values (cond ((<= code #xff) (code-char code))
|
|
((<= #xd800 code #xdfff)
|
|
(error "Invalid Unicode code point: #x~x" code))
|
|
((and (< code char-code-limit)
|
|
(code-char code)))
|
|
(t
|
|
(error
|
|
"Can't represent code point: #x~x ~
|
|
(char-code-limit is #x~x)"
|
|
code char-code-limit)))
|
|
(+ index n))))))
|
|
|
|
;; Decode one character in BUFFER starting at INDEX.
|
|
;; Return 2 values: the character and the new index.
|
|
;; If there aren't enough bytes between INDEX and LIMIT return nil.
|
|
(defun utf8-decode (buffer index limit)
|
|
(declare (type octets buffer) (fixnum index limit))
|
|
(if (= index limit)
|
|
(values nil index)
|
|
(let ((b (aref buffer index)))
|
|
(if (<= b #x7f)
|
|
(values (code-char b) (1+ index))
|
|
(macrolet ((try (marker else)
|
|
(let* ((l (integer-length marker))
|
|
(n (- l 2)))
|
|
`(if (= (ldb (byte ,l ,(- 8 l)) b) ,marker)
|
|
(utf8-decode-aux buffer (1+ index) limit
|
|
(ldb (byte ,(- 8 l) 0) b)
|
|
,n)
|
|
,else))))
|
|
(try #b110
|
|
(try #b1110
|
|
(try #b11110
|
|
(try #b111110
|
|
(try #b1111110
|
|
(error "Invalid encoding")))))))))))
|
|
|
|
;; Decode characters from BUFFER and write them to STRING.
|
|
;; Return 2 values: LASTINDEX and LASTSTART where
|
|
;; LASTINDEX is the last index in BUFFER that was not decoded
|
|
;; and LASTSTART is the last index in STRING not written.
|
|
(defun utf8-decode-into (buffer index limit string start end)
|
|
(declare (string string) (fixnum index limit start end) (type octets buffer))
|
|
(loop
|
|
(cond ((= start end)
|
|
(return (values index start)))
|
|
(t
|
|
(multiple-value-bind (c i) (utf8-decode buffer index limit)
|
|
(cond (c
|
|
(setf (aref string start) c)
|
|
(setq index i)
|
|
(setq start (1+ start)))
|
|
(t
|
|
(return (values index start)))))))))
|
|
|
|
(defun default-utf8-to-string (octets)
|
|
(let* ((limit (length octets))
|
|
(str (make-string limit)))
|
|
(multiple-value-bind (i s) (utf8-decode-into octets 0 limit str 0 limit)
|
|
(if (= i limit)
|
|
(if (= limit s)
|
|
str
|
|
(adjust-array str s))
|
|
(loop
|
|
(let ((end (+ (length str) (- limit i))))
|
|
(setq str (adjust-array str end))
|
|
(multiple-value-bind (i2 s2)
|
|
(utf8-decode-into octets i limit str s end)
|
|
(cond ((= i2 limit)
|
|
(return (adjust-array str s2)))
|
|
(t
|
|
(setq i i2)
|
|
(setq s s2))))))))))
|
|
|
|
(defmacro utf8-encode-aux (code buffer start end n)
|
|
`(cond ((< (- ,end ,start) ,n)
|
|
,start)
|
|
(t
|
|
(setf (aref ,buffer ,start)
|
|
(dpb (ldb (byte ,(- 7 n) ,(* 6 (1- n))) ,code)
|
|
(byte ,(- 7 n) 0)
|
|
,(dpb 0 (byte 1 (- 7 n)) #xff)))
|
|
,@(loop for i from 0 upto (- n 2) collect
|
|
`(setf (aref ,buffer (+ ,start ,(- n 1 i)))
|
|
(dpb (ldb (byte 6 ,(* 6 i)) ,code)
|
|
(byte 6 0)
|
|
#b10111111)))
|
|
(+ ,start ,n))))
|
|
|
|
(defun %utf8-encode (code buffer start end)
|
|
(declare (type (unsigned-byte 31) code) (type octets buffer)
|
|
(type (and fixnum unsigned-byte) start end))
|
|
(cond ((<= code #x7f)
|
|
(cond ((< start end)
|
|
(setf (aref buffer start) code)
|
|
(1+ start))
|
|
(t start)))
|
|
((<= code #x7ff) (utf8-encode-aux code buffer start end 2))
|
|
((<= #xd800 code #xdfff)
|
|
(error "Invalid Unicode code point (surrogate): #x~x" code))
|
|
((<= code #xffff) (utf8-encode-aux code buffer start end 3))
|
|
((<= code #x1fffff) (utf8-encode-aux code buffer start end 4))
|
|
((<= code #x3ffffff) (utf8-encode-aux code buffer start end 5))
|
|
(t (utf8-encode-aux code buffer start end 6))))
|
|
|
|
(defun utf8-encode (char buffer start end)
|
|
(declare (type character char) (type octets buffer)
|
|
(type (and fixnum unsigned-byte) start end))
|
|
(%utf8-encode (char-code char) buffer start end))
|
|
|
|
(defun utf8-encode-into (string start end buffer index limit)
|
|
(declare (string string) (type octets buffer) (fixnum start end index limit))
|
|
(loop
|
|
(cond ((= start end)
|
|
(return (values start index)))
|
|
((= index limit)
|
|
(return (values start index)))
|
|
(t
|
|
(let ((i2 (utf8-encode (char string start) buffer index limit)))
|
|
(cond ((= i2 index)
|
|
(return (values start index)))
|
|
(t
|
|
(setq index i2)
|
|
(incf start))))))))
|
|
|
|
(defun default-string-to-utf8 (string)
|
|
(let* ((len (length string))
|
|
(b (make-array len :element-type 'octet)))
|
|
(multiple-value-bind (s i) (utf8-encode-into string 0 len b 0 len)
|
|
(if (= s len)
|
|
b
|
|
(loop
|
|
(let ((limit (+ (length b) (- len s))))
|
|
(setq b (coerce (adjust-array b limit) 'octets))
|
|
(multiple-value-bind (s2 i2)
|
|
(utf8-encode-into string s len b i limit)
|
|
(cond ((= s2 len)
|
|
(return (coerce (adjust-array b i2) 'octets)))
|
|
(t
|
|
(setq i i2)
|
|
(setq s s2))))))))))
|
|
|
|
(definterface string-to-utf8 (string)
|
|
"Convert the string STRING to a (simple-array (unsigned-byte 8))"
|
|
(default-string-to-utf8 string))
|
|
|
|
(definterface utf8-to-string (octets)
|
|
"Convert the (simple-array (unsigned-byte 8)) OCTETS to a string."
|
|
(default-utf8-to-string octets))
|
|
|
|
|
|
;;;; TCP server
|
|
|
|
(definterface create-socket (host port &key backlog)
|
|
"Create a listening TCP socket on interface HOST and port PORT.
|
|
BACKLOG queue length for incoming connections.")
|
|
|
|
(definterface local-port (socket)
|
|
"Return the local port number of SOCKET.")
|
|
|
|
(definterface close-socket (socket)
|
|
"Close the socket SOCKET.")
|
|
|
|
(definterface accept-connection (socket &key external-format
|
|
buffering timeout)
|
|
"Accept a client connection on the listening socket SOCKET.
|
|
Return a stream for the new connection.
|
|
If EXTERNAL-FORMAT is nil return a binary stream
|
|
otherwise create a character stream.
|
|
BUFFERING can be one of:
|
|
nil ... no buffering
|
|
t ... enable buffering
|
|
:line ... enable buffering with automatic flushing on eol.")
|
|
|
|
(definterface add-sigio-handler (socket fn)
|
|
"Call FN whenever SOCKET is readable.")
|
|
|
|
(definterface remove-sigio-handlers (socket)
|
|
"Remove all sigio handlers for SOCKET.")
|
|
|
|
(definterface add-fd-handler (socket fn)
|
|
"Call FN when Lisp is waiting for input and SOCKET is readable.")
|
|
|
|
(definterface remove-fd-handlers (socket)
|
|
"Remove all fd-handlers for SOCKET.")
|
|
|
|
(definterface preferred-communication-style ()
|
|
"Return one of the symbols :spawn, :sigio, :fd-handler, or NIL."
|
|
nil)
|
|
|
|
(definterface set-stream-timeout (stream timeout)
|
|
"Set the 'stream 'timeout. The timeout is either the real number
|
|
specifying the timeout in seconds or 'nil for no timeout."
|
|
(declare (ignore stream timeout))
|
|
nil)
|
|
|
|
;;; Base condition for networking errors.
|
|
(define-condition network-error (simple-error) ())
|
|
|
|
(definterface emacs-connected ()
|
|
"Hook called when the first connection from Emacs is established.
|
|
Called from the INIT-FN of the socket server that accepts the
|
|
connection.
|
|
|
|
This is intended for setting up extra context, e.g. to discover
|
|
that the calling thread is the one that interacts with Emacs."
|
|
nil)
|
|
|
|
|
|
;;;; Unix signals
|
|
|
|
(defconstant +sigint+ 2)
|
|
|
|
(definterface getpid ()
|
|
"Return the (Unix) process ID of this superior Lisp.")
|
|
|
|
(definterface install-sigint-handler (function)
|
|
"Call FUNCTION on SIGINT (instead of invoking the debugger).
|
|
Return old signal handler."
|
|
(declare (ignore function))
|
|
nil)
|
|
|
|
(definterface call-with-user-break-handler (handler function)
|
|
"Install the break handler HANDLER while executing FUNCTION."
|
|
(let ((old-handler (install-sigint-handler handler)))
|
|
(unwind-protect (funcall function)
|
|
(install-sigint-handler old-handler))))
|
|
|
|
(definterface quit-lisp ()
|
|
"Exit the current lisp image.")
|
|
|
|
(definterface lisp-implementation-type-name ()
|
|
"Return a short name for the Lisp implementation."
|
|
(lisp-implementation-type))
|
|
|
|
(definterface lisp-implementation-program ()
|
|
"Return the argv[0] of the running Lisp process, or NIL."
|
|
(let ((file (car (command-line-args))))
|
|
(when (and file (probe-file file))
|
|
(namestring (truename file)))))
|
|
|
|
(definterface socket-fd (socket-stream)
|
|
"Return the file descriptor for SOCKET-STREAM.")
|
|
|
|
(definterface make-fd-stream (fd external-format)
|
|
"Create a character stream for the file descriptor FD.")
|
|
|
|
(definterface dup (fd)
|
|
"Duplicate a file descriptor.
|
|
If the syscall fails, signal a condition.
|
|
See dup(2).")
|
|
|
|
(definterface exec-image (image-file args)
|
|
"Replace the current process with a new process image.
|
|
The new image is created by loading the previously dumped
|
|
core file IMAGE-FILE.
|
|
ARGS is a list of strings passed as arguments to
|
|
the new image.
|
|
This is thin wrapper around exec(3).")
|
|
|
|
(definterface command-line-args ()
|
|
"Return a list of strings as passed by the OS."
|
|
nil)
|
|
|
|
|
|
;; pathnames are sooo useless
|
|
|
|
(definterface filename-to-pathname (filename)
|
|
"Return a pathname for FILENAME.
|
|
A filename in Emacs may for example contain asterisks which should not
|
|
be translated to wildcards."
|
|
(parse-namestring filename))
|
|
|
|
(definterface pathname-to-filename (pathname)
|
|
"Return the filename for PATHNAME."
|
|
(namestring pathname))
|
|
|
|
(definterface default-directory ()
|
|
"Return the default directory."
|
|
(directory-namestring (truename *default-pathname-defaults*)))
|
|
|
|
(definterface set-default-directory (directory)
|
|
"Set the default directory.
|
|
This is used to resolve filenames without directory component."
|
|
(setf *default-pathname-defaults* (truename (merge-pathnames directory)))
|
|
(default-directory))
|
|
|
|
|
|
(definterface call-with-syntax-hooks (fn)
|
|
"Call FN with hooks to handle special syntax."
|
|
(funcall fn))
|
|
|
|
(definterface default-readtable-alist ()
|
|
"Return a suitable initial value for SWANK:*READTABLE-ALIST*."
|
|
'())
|
|
|
|
|
|
;;;; Packages
|
|
|
|
(definterface package-local-nicknames (package)
|
|
"Returns an alist of (local-nickname . actual-package) describing the
|
|
nicknames local to the designated package."
|
|
(declare (ignore package))
|
|
nil)
|
|
|
|
(definterface find-locally-nicknamed-package (name base-package)
|
|
"Return the package whose local nickname in BASE-PACKAGE matches NAME.
|
|
Return NIL if local nicknames are not implemented or if there is no
|
|
such package."
|
|
(cdr (assoc name (package-local-nicknames base-package) :test #'string-equal)))
|
|
|
|
|
|
;;;; Compilation
|
|
|
|
(definterface call-with-compilation-hooks (func)
|
|
"Call FUNC with hooks to record compiler conditions.")
|
|
|
|
(defmacro with-compilation-hooks ((&rest ignore) &body body)
|
|
"Execute BODY as in CALL-WITH-COMPILATION-HOOKS."
|
|
(declare (ignore ignore))
|
|
`(call-with-compilation-hooks (lambda () (progn ,@body))))
|
|
|
|
(definterface swank-compile-string (string &key buffer position filename
|
|
policy)
|
|
"Compile source from STRING.
|
|
During compilation, compiler conditions must be trapped and
|
|
resignalled as COMPILER-CONDITIONs.
|
|
|
|
If supplied, BUFFER and POSITION specify the source location in Emacs.
|
|
|
|
Additionally, if POSITION is supplied, it must be added to source
|
|
positions reported in compiler conditions.
|
|
|
|
If FILENAME is specified it may be used by certain implementations to
|
|
rebind *DEFAULT-PATHNAME-DEFAULTS* which may improve the recording of
|
|
source information.
|
|
|
|
If POLICY is supplied, and non-NIL, it may be used by certain
|
|
implementations to compile with optimization qualities of its
|
|
value.
|
|
|
|
Should return T on successful compilation, NIL otherwise.
|
|
")
|
|
|
|
(definterface swank-compile-file (input-file output-file load-p
|
|
external-format
|
|
&key policy)
|
|
"Compile INPUT-FILE signalling COMPILE-CONDITIONs.
|
|
If LOAD-P is true, load the file after compilation.
|
|
EXTERNAL-FORMAT is a value returned by find-external-format or
|
|
:default.
|
|
|
|
If POLICY is supplied, and non-NIL, it may be used by certain
|
|
implementations to compile with optimization qualities of its
|
|
value.
|
|
|
|
Should return OUTPUT-TRUENAME, WARNINGS-P and FAILURE-p
|
|
like `compile-file'")
|
|
|
|
(deftype severity ()
|
|
'(member :error :read-error :warning :style-warning :note :redefinition))
|
|
|
|
;; Base condition type for compiler errors, warnings and notes.
|
|
(define-condition compiler-condition (condition)
|
|
((original-condition
|
|
;; The original condition thrown by the compiler if appropriate.
|
|
;; May be NIL if a compiler does not report using conditions.
|
|
:type (or null condition)
|
|
:initarg :original-condition
|
|
:accessor original-condition)
|
|
|
|
(severity :type severity
|
|
:initarg :severity
|
|
:accessor severity)
|
|
|
|
(message :initarg :message
|
|
:accessor message)
|
|
|
|
;; Macro expansion history etc. which may be helpful in some cases
|
|
;; but is often very verbose.
|
|
(source-context :initarg :source-context
|
|
:type (or null string)
|
|
:initform nil
|
|
:accessor source-context)
|
|
|
|
(references :initarg :references
|
|
:initform nil
|
|
:accessor references)
|
|
|
|
(location :initarg :location
|
|
:accessor location)))
|
|
|
|
(definterface find-external-format (coding-system)
|
|
"Return a \"external file format designator\" for CODING-SYSTEM.
|
|
CODING-SYSTEM is Emacs-style coding system name (a string),
|
|
e.g. \"latin-1-unix\"."
|
|
(if (equal coding-system "iso-latin-1-unix")
|
|
:default
|
|
nil))
|
|
|
|
(definterface guess-external-format (pathname)
|
|
"Detect the external format for the file with name pathname.
|
|
Return nil if the file contains no special markers."
|
|
;; Look for a Emacs-style -*- coding: ... -*- or Local Variable: section.
|
|
(with-open-file (s pathname :if-does-not-exist nil
|
|
:external-format (or (find-external-format "latin-1-unix")
|
|
:default))
|
|
(if s
|
|
(or (let* ((line (read-line s nil))
|
|
(p (search "-*-" line)))
|
|
(when p
|
|
(let* ((start (+ p (length "-*-")))
|
|
(end (search "-*-" line :start2 start)))
|
|
(when end
|
|
(%search-coding line start end)))))
|
|
(let* ((len (file-length s))
|
|
(buf (make-string (min len 3000))))
|
|
(file-position s (- len (length buf)))
|
|
(read-sequence buf s)
|
|
(let ((start (search "Local Variables:" buf :from-end t))
|
|
(end (search "End:" buf :from-end t)))
|
|
(and start end (< start end)
|
|
(%search-coding buf start end))))))))
|
|
|
|
(defun %search-coding (str start end)
|
|
(let ((p (search "coding:" str :start2 start :end2 end)))
|
|
(when p
|
|
(incf p (length "coding:"))
|
|
(loop while (and (< p end)
|
|
(member (aref str p) '(#\space #\tab)))
|
|
do (incf p))
|
|
(let ((end (position-if (lambda (c) (find c '(#\space #\tab #\newline #\;)))
|
|
str :start p)))
|
|
(find-external-format (subseq str p end))))))
|
|
|
|
|
|
;;;; Streams
|
|
|
|
(definterface make-output-stream (write-string)
|
|
"Return a new character output stream.
|
|
The stream calls WRITE-STRING when output is ready.")
|
|
|
|
(definterface make-input-stream (read-string)
|
|
"Return a new character input stream.
|
|
The stream calls READ-STRING when input is needed.")
|
|
|
|
(defvar *auto-flush-interval* 0.2)
|
|
|
|
(defun auto-flush-loop (stream interval &optional receive)
|
|
(loop
|
|
(when (not (and (open-stream-p stream)
|
|
(output-stream-p stream)))
|
|
(return nil))
|
|
(force-output stream)
|
|
(when receive
|
|
(receive-if #'identity))
|
|
(sleep interval)))
|
|
|
|
(definterface make-auto-flush-thread (stream)
|
|
"Make an auto-flush thread"
|
|
(spawn (lambda () (auto-flush-loop stream *auto-flush-interval* nil))
|
|
:name "auto-flush-thread"))
|
|
|
|
|
|
;;;; Documentation
|
|
|
|
(definterface arglist (name)
|
|
"Return the lambda list for the symbol NAME. NAME can also be
|
|
a lisp function object, on lisps which support this.
|
|
|
|
The result can be a list or the :not-available keyword if the
|
|
arglist cannot be determined."
|
|
(declare (ignore name))
|
|
:not-available)
|
|
|
|
(defgeneric declaration-arglist (decl-identifier)
|
|
(:documentation
|
|
"Return the argument list of the declaration specifier belonging to the
|
|
declaration identifier DECL-IDENTIFIER. If the arglist cannot be determined,
|
|
the keyword :NOT-AVAILABLE is returned.
|
|
|
|
The different SWANK backends can specialize this generic function to
|
|
include implementation-dependend declaration specifiers, or to provide
|
|
additional information on the specifiers defined in ANSI Common Lisp.")
|
|
(:method (decl-identifier)
|
|
(case decl-identifier
|
|
(dynamic-extent '(&rest variables))
|
|
(ignore '(&rest variables))
|
|
(ignorable '(&rest variables))
|
|
(special '(&rest variables))
|
|
(inline '(&rest function-names))
|
|
(notinline '(&rest function-names))
|
|
(declaration '(&rest names))
|
|
(optimize '(&any compilation-speed debug safety space speed))
|
|
(type '(type-specifier &rest args))
|
|
(ftype '(type-specifier &rest function-names))
|
|
(otherwise
|
|
(flet ((typespec-p (symbol)
|
|
(member :type (describe-symbol-for-emacs symbol))))
|
|
(cond ((and (symbolp decl-identifier) (typespec-p decl-identifier))
|
|
'(&rest variables))
|
|
((and (listp decl-identifier)
|
|
(typespec-p (first decl-identifier)))
|
|
'(&rest variables))
|
|
(t :not-available)))))))
|
|
|
|
(defgeneric type-specifier-arglist (typespec-operator)
|
|
(:documentation
|
|
"Return the argument list of the type specifier belonging to
|
|
TYPESPEC-OPERATOR.. If the arglist cannot be determined, the keyword
|
|
:NOT-AVAILABLE is returned.
|
|
|
|
The different SWANK backends can specialize this generic function to
|
|
include implementation-dependend declaration specifiers, or to provide
|
|
additional information on the specifiers defined in ANSI Common Lisp.")
|
|
(:method (typespec-operator)
|
|
(declare (special *type-specifier-arglists*)) ; defined at end of file.
|
|
(typecase typespec-operator
|
|
(symbol (or (cdr (assoc typespec-operator *type-specifier-arglists*))
|
|
:not-available))
|
|
(t :not-available))))
|
|
|
|
(definterface type-specifier-p (symbol)
|
|
"Determine if SYMBOL is a type-specifier."
|
|
(or (documentation symbol 'type)
|
|
(not (eq (type-specifier-arglist symbol) :not-available))))
|
|
|
|
(definterface function-name (function)
|
|
"Return the name of the function object FUNCTION.
|
|
|
|
The result is either a symbol, a list, or NIL if no function name is
|
|
available."
|
|
(declare (ignore function))
|
|
nil)
|
|
|
|
(definterface 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)
|
|
(eq (first form) 'setf) (symbolp (second form))))))
|
|
|
|
(definterface macroexpand-all (form &optional env)
|
|
"Recursively expand all macros in FORM.
|
|
Return the resulting form.")
|
|
|
|
(definterface compiler-macroexpand-1 (form &optional env)
|
|
"Call the compiler-macro for form.
|
|
If FORM is a function call for which a compiler-macro has been
|
|
defined, invoke the expander function using *macroexpand-hook* and
|
|
return the results and T. Otherwise, return the original form and
|
|
NIL."
|
|
(let ((fun (and (consp form)
|
|
(valid-function-name-p (car form))
|
|
(compiler-macro-function (car form) env))))
|
|
(if fun
|
|
(let ((result (funcall *macroexpand-hook* fun form env)))
|
|
(values result (not (eq result form))))
|
|
(values form nil))))
|
|
|
|
(definterface compiler-macroexpand (form &optional env)
|
|
"Repetitively call `compiler-macroexpand-1'."
|
|
(labels ((frob (form expanded)
|
|
(multiple-value-bind (new-form newly-expanded)
|
|
(compiler-macroexpand-1 form env)
|
|
(if newly-expanded
|
|
(frob new-form t)
|
|
(values new-form expanded)))))
|
|
(frob form env)))
|
|
|
|
(defmacro with-collected-macro-forms
|
|
((forms &optional result) instrumented-form &body body)
|
|
"Collect macro forms by locally binding *MACROEXPAND-HOOK*.
|
|
|
|
Evaluates INSTRUMENTED-FORM and collects any forms which undergo
|
|
macro-expansion into a list. Then evaluates BODY with FORMS bound to
|
|
the list of forms, and RESULT (optionally) bound to the value of
|
|
INSTRUMENTED-FORM."
|
|
(assert (and (symbolp forms) (not (null forms))))
|
|
(assert (symbolp result))
|
|
(let ((result-symbol (or result (gensym))))
|
|
`(call-with-collected-macro-forms
|
|
(lambda (,forms ,result-symbol)
|
|
(declare (ignore ,@(and (not result)
|
|
`(,result-symbol))))
|
|
,@body)
|
|
(lambda () ,instrumented-form))))
|
|
|
|
(defun call-with-collected-macro-forms (body-fn instrumented-fn)
|
|
(let ((return-value nil)
|
|
(collected-forms '()))
|
|
(let* ((real-macroexpand-hook *macroexpand-hook*)
|
|
(*macroexpand-hook*
|
|
(lambda (macro-function form environment)
|
|
(let ((result (funcall real-macroexpand-hook
|
|
macro-function form environment)))
|
|
(unless (eq result form)
|
|
(push form collected-forms))
|
|
result))))
|
|
(setf return-value (funcall instrumented-fn)))
|
|
(funcall body-fn collected-forms return-value)))
|
|
|
|
(definterface collect-macro-forms (form &optional env)
|
|
"Collect subforms of FORM which undergo (compiler-)macro expansion.
|
|
Returns two values: a list of macro forms and a list of compiler macro
|
|
forms."
|
|
(with-collected-macro-forms (macro-forms expansion)
|
|
(ignore-errors (macroexpand-all form env))
|
|
(with-collected-macro-forms (compiler-macro-forms)
|
|
(handler-bind ((warning #'muffle-warning))
|
|
(ignore-errors
|
|
(compile nil `(lambda () ,expansion))))
|
|
(values macro-forms compiler-macro-forms))))
|
|
|
|
(definterface format-string-expand (control-string)
|
|
"Expand the format string CONTROL-STRING."
|
|
(macroexpand `(formatter ,control-string)))
|
|
|
|
(definterface describe-symbol-for-emacs (symbol)
|
|
"Return a property list describing SYMBOL.
|
|
|
|
The property list has an entry for each interesting aspect of the
|
|
symbol. The recognised keys are:
|
|
|
|
:VARIABLE :FUNCTION :SETF :SPECIAL-OPERATOR :MACRO :COMPILER-MACRO
|
|
:TYPE :CLASS :ALIEN-TYPE :ALIEN-STRUCT :ALIEN-UNION :ALIEN-ENUM
|
|
|
|
The value of each property is the corresponding documentation string,
|
|
or NIL (or the obsolete :NOT-DOCUMENTED). It is legal to include keys
|
|
not listed here (but slime-print-apropos in Emacs must know about
|
|
them).
|
|
|
|
Properties should be included if and only if they are applicable to
|
|
the symbol. For example, only (and all) fbound symbols should include
|
|
the :FUNCTION property.
|
|
|
|
Example:
|
|
\(describe-symbol-for-emacs 'vector)
|
|
=> (:CLASS :NOT-DOCUMENTED
|
|
:TYPE :NOT-DOCUMENTED
|
|
:FUNCTION \"Constructs a simple-vector from the given objects.\")")
|
|
|
|
(definterface describe-definition (name type)
|
|
"Describe the definition NAME of TYPE.
|
|
TYPE can be any value returned by DESCRIBE-SYMBOL-FOR-EMACS.
|
|
|
|
Return a documentation string, or NIL if none is available.")
|
|
|
|
|
|
;;;; Debugging
|
|
|
|
(definterface install-debugger-globally (function)
|
|
"Install FUNCTION as the debugger for all threads/processes. This
|
|
usually involves setting *DEBUGGER-HOOK* and, if the implementation
|
|
permits, hooking into BREAK as well."
|
|
(setq *debugger-hook* function))
|
|
|
|
(definterface call-with-debugging-environment (debugger-loop-fn)
|
|
"Call DEBUGGER-LOOP-FN in a suitable debugging environment.
|
|
|
|
This function is called recursively at each debug level to invoke the
|
|
debugger loop. The purpose is to setup any necessary environment for
|
|
other debugger callbacks that will be called within the debugger loop.
|
|
|
|
For example, this is a reasonable place to compute a backtrace, switch
|
|
to safe reader/printer settings, and so on.")
|
|
|
|
(definterface call-with-debugger-hook (hook fun)
|
|
"Call FUN and use HOOK as debugger hook. HOOK can be NIL.
|
|
|
|
HOOK should be called for both BREAK and INVOKE-DEBUGGER."
|
|
(let ((*debugger-hook* hook))
|
|
(funcall fun)))
|
|
|
|
(define-condition sldb-condition (condition)
|
|
((original-condition
|
|
:initarg :original-condition
|
|
:accessor original-condition))
|
|
(:report (lambda (condition stream)
|
|
(format stream "Condition in debugger code~@[: ~A~]"
|
|
(original-condition condition))))
|
|
(:documentation
|
|
"Wrapper for conditions that should not be debugged.
|
|
|
|
When a condition arises from the internals of the debugger, it is not
|
|
desirable to debug it -- we'd risk entering an endless loop trying to
|
|
debug the debugger! Instead, such conditions can be reported to the
|
|
user without (re)entering the debugger by wrapping them as
|
|
`sldb-condition's."))
|
|
|
|
;;; The following functions in this section are supposed to be called
|
|
;;; within the dynamic contour of CALL-WITH-DEBUGGING-ENVIRONMENT only.
|
|
|
|
(definterface compute-backtrace (start end)
|
|
"Returns a backtrace of the condition currently being debugged,
|
|
that is an ordered list consisting of frames. ``Ordered list''
|
|
means that an integer I can be mapped back to the i-th frame of this
|
|
backtrace.
|
|
|
|
START and END are zero-based indices constraining the number of frames
|
|
returned. Frame zero is defined as the frame which invoked the
|
|
debugger. If END is nil, return the frames from START to the end of
|
|
the stack.")
|
|
|
|
(definterface print-frame (frame stream)
|
|
"Print frame to stream.")
|
|
|
|
(definterface frame-restartable-p (frame)
|
|
"Is the frame FRAME restartable?.
|
|
Return T if `restart-frame' can safely be called on the frame."
|
|
(declare (ignore frame))
|
|
nil)
|
|
|
|
(definterface frame-source-location (frame-number)
|
|
"Return the source location for the frame associated to FRAME-NUMBER.")
|
|
|
|
(definterface frame-catch-tags (frame-number)
|
|
"Return a list of catch tags for being printed in a debugger stack
|
|
frame."
|
|
(declare (ignore frame-number))
|
|
'())
|
|
|
|
(definterface frame-locals (frame-number)
|
|
"Return a list of ((&key NAME ID VALUE) ...) where each element of
|
|
the list represents a local variable in the stack frame associated to
|
|
FRAME-NUMBER.
|
|
|
|
NAME, a symbol; the name of the local variable.
|
|
|
|
ID, an integer; used as primary key for the local variable, unique
|
|
relatively to the frame under operation.
|
|
|
|
value, an object; the value of the local variable.")
|
|
|
|
(definterface frame-var-value (frame-number var-id)
|
|
"Return the value of the local variable associated to VAR-ID
|
|
relatively to the frame associated to FRAME-NUMBER.")
|
|
|
|
(definterface disassemble-frame (frame-number)
|
|
"Disassemble the code for the FRAME-NUMBER.
|
|
The output should be written to standard output.
|
|
FRAME-NUMBER is a non-negative integer.")
|
|
|
|
(definterface eval-in-frame (form frame-number)
|
|
"Evaluate a Lisp form in the lexical context of a stack frame
|
|
in the debugger.
|
|
|
|
FRAME-NUMBER must be a positive integer with 0 indicating the
|
|
frame which invoked the debugger.
|
|
|
|
The return value is the result of evaulating FORM in the
|
|
appropriate context.")
|
|
|
|
(definterface frame-package (frame-number)
|
|
"Return the package corresponding to the frame at FRAME-NUMBER.
|
|
Return nil if the backend can't figure it out."
|
|
(declare (ignore frame-number))
|
|
nil)
|
|
|
|
(definterface frame-call (frame-number)
|
|
"Return a string representing a call to the entry point of a frame.")
|
|
|
|
(definterface return-from-frame (frame-number form)
|
|
"Unwind the stack to the frame FRAME-NUMBER and return the value(s)
|
|
produced by evaluating FORM in the frame context to its caller.
|
|
|
|
Execute any clean-up code from unwind-protect forms above the frame
|
|
during unwinding.
|
|
|
|
Return a string describing the error if it's not possible to return
|
|
from the frame.")
|
|
|
|
(definterface restart-frame (frame-number)
|
|
"Restart execution of the frame FRAME-NUMBER with the same arguments
|
|
as it was called originally.")
|
|
|
|
(definterface print-condition (condition stream)
|
|
"Print a condition for display in SLDB."
|
|
(princ condition stream))
|
|
|
|
(definterface condition-extras (condition)
|
|
"Return a list of extra for the debugger.
|
|
The allowed elements are of the form:
|
|
(:SHOW-FRAME-SOURCE frame-number)
|
|
(:REFERENCES &rest refs)
|
|
"
|
|
(declare (ignore condition))
|
|
'())
|
|
|
|
(definterface gdb-initial-commands ()
|
|
"List of gdb commands supposed to be executed first for the
|
|
ATTACH-GDB restart."
|
|
nil)
|
|
|
|
(definterface activate-stepping (frame-number)
|
|
"Prepare the frame FRAME-NUMBER for stepping.")
|
|
|
|
(definterface sldb-break-on-return (frame-number)
|
|
"Set a breakpoint in the frame FRAME-NUMBER.")
|
|
|
|
(definterface sldb-break-at-start (symbol)
|
|
"Set a breakpoint on the beginning of the function for SYMBOL.")
|
|
|
|
(definterface sldb-stepper-condition-p (condition)
|
|
"Return true if SLDB was invoked due to a single-stepping condition,
|
|
false otherwise. "
|
|
(declare (ignore condition))
|
|
nil)
|
|
|
|
(definterface sldb-step-into ()
|
|
"Step into the current single-stepper form.")
|
|
|
|
(definterface sldb-step-next ()
|
|
"Step to the next form in the current function.")
|
|
|
|
(definterface sldb-step-out ()
|
|
"Stop single-stepping temporarily, but resume it once the current function
|
|
returns.")
|
|
|
|
|
|
;;;; Definition finding
|
|
|
|
(defstruct (location (:type list)
|
|
(:constructor make-location
|
|
(buffer position &optional hints)))
|
|
(type :location)
|
|
buffer position
|
|
;; Hints is a property list optionally containing:
|
|
;; :snippet SOURCE-TEXT
|
|
;; This is a snippet of the actual source text at the start of
|
|
;; the definition, which could be used in a text search.
|
|
hints)
|
|
|
|
(defmacro converting-errors-to-error-location (&body body)
|
|
"Catches errors during BODY and converts them to an error location."
|
|
(let ((gblock (gensym "CONVERTING-ERRORS+")))
|
|
`(block ,gblock
|
|
(handler-bind ((error
|
|
#'(lambda (e)
|
|
(if *debug-swank-backend*
|
|
nil ;decline
|
|
(return-from ,gblock
|
|
(make-error-location e))))))
|
|
,@body))))
|
|
|
|
(defun make-error-location (datum &rest args)
|
|
(cond ((typep datum 'condition)
|
|
`(:error ,(format nil "Error: ~A" datum)))
|
|
((symbolp datum)
|
|
`(:error ,(format nil "Error: ~A"
|
|
(apply #'make-condition datum args))))
|
|
(t
|
|
(assert (stringp datum))
|
|
`(:error ,(apply #'format nil datum args)))))
|
|
|
|
(definterface find-definitions (name)
|
|
"Return a list ((DSPEC LOCATION) ...) for NAME's definitions.
|
|
|
|
NAME is a \"definition specifier\".
|
|
|
|
DSPEC is a \"definition specifier\" describing the
|
|
definition, e.g., FOO or (METHOD FOO (STRING NUMBER)) or
|
|
\(DEFVAR FOO).
|
|
|
|
LOCATION is the source location for the definition.")
|
|
|
|
(definterface find-source-location (object)
|
|
"Returns the source location of OBJECT, or NIL.
|
|
|
|
That is the source location of the underlying datastructure of
|
|
OBJECT. E.g. on a STANDARD-OBJECT, the source location of the
|
|
respective DEFCLASS definition is returned, on a STRUCTURE-CLASS the
|
|
respective DEFSTRUCT definition, and so on."
|
|
;; This returns one source location and not a list of locations. It's
|
|
;; supposed to return the location of the DEFGENERIC definition on
|
|
;; #'SOME-GENERIC-FUNCTION.
|
|
(declare (ignore object))
|
|
(make-error-location "FIND-SOURCE-LOCATION is not yet implemented on ~
|
|
this implementation."))
|
|
|
|
(definterface buffer-first-change (filename)
|
|
"Called for effect the first time FILENAME's buffer is modified.
|
|
CMUCL/SBCL use this to cache the unmodified file and use the
|
|
unmodified text to improve the precision of source locations."
|
|
(declare (ignore filename))
|
|
nil)
|
|
|
|
|
|
|
|
;;;; XREF
|
|
|
|
(definterface who-calls (function-name)
|
|
"Return the call sites of FUNCTION-NAME (a symbol).
|
|
The results is a list ((DSPEC LOCATION) ...)."
|
|
(declare (ignore function-name))
|
|
:not-implemented)
|
|
|
|
(definterface calls-who (function-name)
|
|
"Return the call sites of FUNCTION-NAME (a symbol).
|
|
The results is a list ((DSPEC LOCATION) ...)."
|
|
(declare (ignore function-name))
|
|
:not-implemented)
|
|
|
|
(definterface who-references (variable-name)
|
|
"Return the locations where VARIABLE-NAME (a symbol) is referenced.
|
|
See WHO-CALLS for a description of the return value."
|
|
(declare (ignore variable-name))
|
|
:not-implemented)
|
|
|
|
(definterface who-binds (variable-name)
|
|
"Return the locations where VARIABLE-NAME (a symbol) is bound.
|
|
See WHO-CALLS for a description of the return value."
|
|
(declare (ignore variable-name))
|
|
:not-implemented)
|
|
|
|
(definterface who-sets (variable-name)
|
|
"Return the locations where VARIABLE-NAME (a symbol) is set.
|
|
See WHO-CALLS for a description of the return value."
|
|
(declare (ignore variable-name))
|
|
:not-implemented)
|
|
|
|
(definterface who-macroexpands (macro-name)
|
|
"Return the locations where MACRO-NAME (a symbol) is expanded.
|
|
See WHO-CALLS for a description of the return value."
|
|
(declare (ignore macro-name))
|
|
:not-implemented)
|
|
|
|
(definterface who-specializes (class-name)
|
|
"Return the locations where CLASS-NAME (a symbol) is specialized.
|
|
See WHO-CALLS for a description of the return value."
|
|
(declare (ignore class-name))
|
|
:not-implemented)
|
|
|
|
;;; Simpler variants.
|
|
|
|
(definterface list-callers (function-name)
|
|
"List the callers of FUNCTION-NAME.
|
|
This function is like WHO-CALLS except that it is expected to use
|
|
lower-level means. Whereas WHO-CALLS is usually implemented with
|
|
special compiler support, LIST-CALLERS is usually implemented by
|
|
groveling for constants in function objects throughout the heap.
|
|
|
|
The return value is as for WHO-CALLS.")
|
|
|
|
(definterface list-callees (function-name)
|
|
"List the functions called by FUNCTION-NAME.
|
|
See LIST-CALLERS for a description of the return value.")
|
|
|
|
|
|
;;;; Profiling
|
|
|
|
;;; The following functions define a minimal profiling interface.
|
|
|
|
(definterface profile (fname)
|
|
"Marks symbol FNAME for profiling.")
|
|
|
|
(definterface profiled-functions ()
|
|
"Returns a list of profiled functions.")
|
|
|
|
(definterface unprofile (fname)
|
|
"Marks symbol FNAME as not profiled.")
|
|
|
|
(definterface unprofile-all ()
|
|
"Marks all currently profiled functions as not profiled."
|
|
(dolist (f (profiled-functions))
|
|
(unprofile f)))
|
|
|
|
(definterface profile-report ()
|
|
"Prints profile report.")
|
|
|
|
(definterface profile-reset ()
|
|
"Resets profile counters.")
|
|
|
|
(definterface profile-package (package callers-p methods)
|
|
"Wrap profiling code around all functions in PACKAGE. If a function
|
|
is already profiled, then unprofile and reprofile (useful to notice
|
|
function redefinition.)
|
|
|
|
If CALLERS-P is T names have counts of the most common calling
|
|
functions recorded.
|
|
|
|
When called with arguments :METHODS T, profile all methods of all
|
|
generic functions having names in the given package. Generic functions
|
|
themselves, that is, their dispatch functions, are left alone.")
|
|
|
|
|
|
;;;; Trace
|
|
|
|
(definterface toggle-trace (spec)
|
|
"Toggle tracing of the function(s) given with SPEC.
|
|
SPEC can be:
|
|
(setf NAME) ; a setf function
|
|
(:defmethod NAME QUALIFIER... (SPECIALIZER...)) ; a specific method
|
|
(:defgeneric NAME) ; a generic function with all methods
|
|
(:call CALLER CALLEE) ; trace calls from CALLER to CALLEE.
|
|
(:labels TOPLEVEL LOCAL)
|
|
(:flet TOPLEVEL LOCAL) ")
|
|
|
|
|
|
;;;; Inspector
|
|
|
|
(defgeneric emacs-inspect (object)
|
|
(:documentation
|
|
"Explain to Emacs how to inspect OBJECT.
|
|
|
|
Returns a list specifying how to render the object for inspection.
|
|
|
|
Every element of the list must be either a string, which will be
|
|
inserted into the buffer as is, or a list of the form:
|
|
|
|
(:value object &optional format) - Render an inspectable
|
|
object. If format is provided it must be a string and will be
|
|
rendered in place of the value, otherwise use princ-to-string.
|
|
|
|
(:newline) - Render a \\n
|
|
|
|
(:action label lambda &key (refresh t)) - Render LABEL (a text
|
|
string) which when clicked will call LAMBDA. If REFRESH is
|
|
non-NIL the currently inspected object will be re-inspected
|
|
after calling the lambda.
|
|
"))
|
|
|
|
(defmethod emacs-inspect ((object t))
|
|
"Generic method for inspecting any kind of object.
|
|
|
|
Since we don't know how to deal with OBJECT we simply dump the
|
|
output of CL:DESCRIBE."
|
|
`("Type: " (:value ,(type-of object)) (:newline)
|
|
"Don't know how to inspect the object, dumping output of CL:DESCRIBE:"
|
|
(:newline) (:newline)
|
|
,(with-output-to-string (desc) (describe object desc))))
|
|
|
|
(definterface eval-context (object)
|
|
"Return a list of bindings corresponding to OBJECT's slots."
|
|
(declare (ignore object))
|
|
'())
|
|
|
|
;;; Utilities for inspector methods.
|
|
;;;
|
|
|
|
(defun label-value-line (label value &key (newline t))
|
|
"Create a control list which prints \"LABEL: VALUE\" in the inspector.
|
|
If NEWLINE is non-NIL a `(:newline)' is added to the result."
|
|
(list* (princ-to-string label) ": " `(:value ,value)
|
|
(if newline '((:newline)) nil)))
|
|
|
|
(defmacro label-value-line* (&rest label-values)
|
|
` (append ,@(loop for (label value) in label-values
|
|
collect `(label-value-line ,label ,value))))
|
|
|
|
(definterface describe-primitive-type (object)
|
|
"Return a string describing the primitive type of object."
|
|
(declare (ignore object))
|
|
"N/A")
|
|
|
|
|
|
;;;; Multithreading
|
|
;;;
|
|
;;; The default implementations are sufficient for non-multiprocessing
|
|
;;; implementations.
|
|
|
|
(definterface initialize-multiprocessing (continuation)
|
|
"Initialize multiprocessing, if necessary and then invoke CONTINUATION.
|
|
|
|
Depending on the impleimentaion, this function may never return."
|
|
(funcall continuation))
|
|
|
|
(definterface spawn (fn &key name)
|
|
"Create a new thread to call FN.")
|
|
|
|
(definterface thread-id (thread)
|
|
"Return an Emacs-parsable object to identify THREAD.
|
|
|
|
Ids should be comparable with equal, i.e.:
|
|
(equal (thread-id <t1>) (thread-id <t2>)) <==> (eq <t1> <t2>)"
|
|
thread)
|
|
|
|
(definterface find-thread (id)
|
|
"Return the thread for ID.
|
|
ID should be an id previously obtained with THREAD-ID.
|
|
Can return nil if the thread no longer exists."
|
|
(declare (ignore id))
|
|
(current-thread))
|
|
|
|
(definterface thread-name (thread)
|
|
"Return the name of THREAD.
|
|
Thread names are short strings meaningful to the user. They do not
|
|
have to be unique."
|
|
(declare (ignore thread))
|
|
"The One True Thread")
|
|
|
|
(definterface thread-status (thread)
|
|
"Return a string describing THREAD's state."
|
|
(declare (ignore thread))
|
|
"")
|
|
|
|
(definterface thread-attributes (thread)
|
|
"Return a plist of implementation-dependent attributes for THREAD"
|
|
(declare (ignore thread))
|
|
'())
|
|
|
|
(definterface current-thread ()
|
|
"Return the currently executing thread."
|
|
0)
|
|
|
|
(definterface all-threads ()
|
|
"Return a fresh list of all threads."
|
|
'())
|
|
|
|
(definterface thread-alive-p (thread)
|
|
"Test if THREAD is termintated."
|
|
(member thread (all-threads)))
|
|
|
|
(definterface interrupt-thread (thread fn)
|
|
"Cause THREAD to execute FN.")
|
|
|
|
(definterface kill-thread (thread)
|
|
"Terminate THREAD immediately.
|
|
Don't execute unwind-protected sections, don't raise conditions.
|
|
(Do not pass go, do not collect $200.)"
|
|
(declare (ignore thread))
|
|
nil)
|
|
|
|
(definterface send (thread object)
|
|
"Send OBJECT to thread THREAD."
|
|
(declare (ignore thread))
|
|
object)
|
|
|
|
(definterface receive (&optional timeout)
|
|
"Return the next message from current thread's mailbox."
|
|
(receive-if (constantly t) timeout))
|
|
|
|
(definterface receive-if (predicate &optional timeout)
|
|
"Return the first message satisfiying PREDICATE.")
|
|
|
|
(definterface wake-thread (thread)
|
|
"Trigger a call to CHECK-SLIME-INTERRUPTS in THREAD without using
|
|
asynchronous interrupts."
|
|
(declare (ignore thread))
|
|
;; Doesn't have to implement this if RECEIVE-IF periodically calls
|
|
;; CHECK-SLIME-INTERRUPTS, but that's energy inefficient
|
|
nil)
|
|
|
|
(definterface register-thread (name thread)
|
|
"Associate the thread THREAD with the symbol NAME.
|
|
The thread can then be retrieved with `find-registered'.
|
|
If THREAD is nil delete the association."
|
|
(declare (ignore name thread))
|
|
nil)
|
|
|
|
(definterface find-registered (name)
|
|
"Find the thread that was registered for the symbol NAME.
|
|
Return nil if the no thread was registred or if the tread is dead."
|
|
(declare (ignore name))
|
|
nil)
|
|
|
|
(definterface set-default-initial-binding (var form)
|
|
"Initialize special variable VAR by default with FORM.
|
|
|
|
Some implementations initialize certain variables in each newly
|
|
created thread. This function sets the form which is used to produce
|
|
the initial value."
|
|
(set var (eval form)))
|
|
|
|
;; List of delayed interrupts.
|
|
;; This should only have thread-local bindings, so no init form.
|
|
(defvar *pending-slime-interrupts*)
|
|
|
|
(defun check-slime-interrupts ()
|
|
"Execute pending interrupts if any.
|
|
This should be called periodically in operations which
|
|
can take a long time to complete.
|
|
Return a boolean indicating whether any interrupts was processed."
|
|
(when (and (boundp '*pending-slime-interrupts*)
|
|
*pending-slime-interrupts*)
|
|
(funcall (pop *pending-slime-interrupts*))
|
|
t))
|
|
|
|
(defvar *interrupt-queued-handler* nil
|
|
"Function to call on queued interrupts.
|
|
Interrupts get queued when an interrupt occurs while interrupt
|
|
handling is disabled.
|
|
|
|
Backends can use this function to abort slow operations.")
|
|
|
|
(definterface wait-for-input (streams &optional timeout)
|
|
"Wait for input on a list of streams. Return those that are ready.
|
|
STREAMS is a list of streams
|
|
TIMEOUT nil, t, or real number. If TIMEOUT is t, return those streams
|
|
which are ready (or have reached end-of-file) without waiting.
|
|
If TIMEOUT is a number and no streams is ready after TIMEOUT seconds,
|
|
return nil.
|
|
|
|
Return :interrupt if an interrupt occurs while waiting."
|
|
(declare (ignore streams timeout))
|
|
;; Invoking the slime debugger will just endlessly loop.
|
|
(call-with-debugger-hook
|
|
nil
|
|
(lambda ()
|
|
(error "~s not implemented. Check if ~s = ~s is supported by the implementation."
|
|
'wait-for-input 'swank:*communication-style* swank:*communication-style*))))
|
|
|
|
|
|
;;;; Locks
|
|
|
|
;; Please use locks only in swank-gray.lisp. Locks are too low-level
|
|
;; for our taste.
|
|
|
|
(definterface make-lock (&key name)
|
|
"Make a lock for thread synchronization.
|
|
Only one thread may hold the lock (via CALL-WITH-LOCK-HELD) at a time
|
|
but that thread may hold it more than once."
|
|
(declare (ignore name))
|
|
:null-lock)
|
|
|
|
(definterface call-with-lock-held (lock function)
|
|
"Call FUNCTION with LOCK held, queueing if necessary."
|
|
(declare (ignore lock)
|
|
(type function function))
|
|
(funcall function))
|
|
|
|
|
|
;;;; Weak datastructures
|
|
|
|
(definterface make-weak-key-hash-table (&rest args)
|
|
"Like MAKE-HASH-TABLE, but weak w.r.t. the keys."
|
|
(apply #'make-hash-table args))
|
|
|
|
(definterface make-weak-value-hash-table (&rest args)
|
|
"Like MAKE-HASH-TABLE, but weak w.r.t. the values."
|
|
(apply #'make-hash-table args))
|
|
|
|
(definterface hash-table-weakness (hashtable)
|
|
"Return nil or one of :key :value :key-or-value :key-and-value"
|
|
(declare (ignore hashtable))
|
|
nil)
|
|
|
|
|
|
;;;; Floating point
|
|
|
|
(definterface float-nan-p (float)
|
|
"Return true if FLOAT is a NaN value (Not a Number)."
|
|
;; When the float type implements IEEE-754 floats, two NaN values
|
|
;; are never equal; when the implementation does not support NaN,
|
|
;; the predicate should return false. An implementation can
|
|
;; implement comparison with "unordered-signaling predicates", which
|
|
;; emit floating point exceptions.
|
|
(handler-case (not (= float float))
|
|
;; Comparisons never signal an exception other than the invalid
|
|
;; operation exception (5.11 Details of comparison predicates).
|
|
(floating-point-invalid-operation () t)))
|
|
|
|
(definterface float-infinity-p (float)
|
|
"Return true if FLOAT is positive or negative infinity."
|
|
(not (< most-negative-long-float
|
|
float
|
|
most-positive-long-float)))
|
|
|
|
|
|
;;;; Character names
|
|
|
|
(definterface character-completion-set (prefix matchp)
|
|
"Return a list of names of characters that match PREFIX."
|
|
;; Handle the standard and semi-standard characters.
|
|
(loop for name in '("Newline" "Space" "Tab" "Page" "Rubout"
|
|
"Linefeed" "Return" "Backspace")
|
|
when (funcall matchp prefix name)
|
|
collect name))
|
|
|
|
|
|
(defparameter *type-specifier-arglists*
|
|
'((and . (&rest type-specifiers))
|
|
(array . (&optional element-type dimension-spec))
|
|
(base-string . (&optional size))
|
|
(bit-vector . (&optional size))
|
|
(complex . (&optional type-specifier))
|
|
(cons . (&optional car-typespec cdr-typespec))
|
|
(double-float . (&optional lower-limit upper-limit))
|
|
(eql . (object))
|
|
(float . (&optional lower-limit upper-limit))
|
|
(function . (&optional arg-typespec value-typespec))
|
|
(integer . (&optional lower-limit upper-limit))
|
|
(long-float . (&optional lower-limit upper-limit))
|
|
(member . (&rest eql-objects))
|
|
(mod . (n))
|
|
(not . (type-specifier))
|
|
(or . (&rest type-specifiers))
|
|
(rational . (&optional lower-limit upper-limit))
|
|
(real . (&optional lower-limit upper-limit))
|
|
(satisfies . (predicate-symbol))
|
|
(short-float . (&optional lower-limit upper-limit))
|
|
(signed-byte . (&optional size))
|
|
(simple-array . (&optional element-type dimension-spec))
|
|
(simple-base-string . (&optional size))
|
|
(simple-bit-vector . (&optional size))
|
|
(simple-string . (&optional size))
|
|
(single-float . (&optional lower-limit upper-limit))
|
|
(simple-vector . (&optional size))
|
|
(string . (&optional size))
|
|
(unsigned-byte . (&optional size))
|
|
(values . (&rest typespecs))
|
|
(vector . (&optional element-type size))
|
|
))
|
|
|
|
;;; Heap dumps
|
|
|
|
(definterface save-image (filename &optional restart-function)
|
|
"Save a heap image to the file FILENAME.
|
|
RESTART-FUNCTION, if non-nil, should be called when the image is loaded.")
|
|
|
|
(definterface background-save-image (filename &key restart-function
|
|
completion-function)
|
|
"Request saving a heap image to the file FILENAME.
|
|
RESTART-FUNCTION, if non-nil, should be called when the image is loaded.
|
|
COMPLETION-FUNCTION, if non-nil, should be called after saving the image.")
|
|
|
|
(defun deinit-log-output ()
|
|
;; Can't hang on to an fd-stream from a previous session.
|
|
(setf *log-output* nil))
|
|
|
|
|
|
;;;; Wrapping
|
|
|
|
(definterface wrap (spec indicator &key before after replace)
|
|
"Intercept future calls to SPEC and surround them in callbacks.
|
|
|
|
INDICATOR is a symbol identifying a particular wrapping, and is used
|
|
to differentiate between multiple wrappings.
|
|
|
|
Implementations intercept calls to SPEC and call, in this order:
|
|
|
|
* the BEFORE callback, if it's provided, with a single argument set to
|
|
the list of arguments passed to the intercepted call;
|
|
|
|
* the original definition of SPEC recursively honouring any wrappings
|
|
previously established under different values of INDICATOR. If the
|
|
compatible function REPLACE is provided, call that instead.
|
|
|
|
* the AFTER callback, if it's provided, with a single set to the list
|
|
of values returned by the previous call, or, if that call exited
|
|
non-locally, a single descriptive symbol, like :EXITED-NON-LOCALLY."
|
|
(declare (ignore indicator))
|
|
(assert (symbolp spec) nil
|
|
"The default implementation for WRAP allows only simple names")
|
|
(assert (null (get spec 'slime-wrap)) nil
|
|
"The default implementation for WRAP allows a single wrapping")
|
|
(let* ((saved (symbol-function spec))
|
|
(replacement (lambda (&rest args)
|
|
(let (retlist completed)
|
|
(unwind-protect
|
|
(progn
|
|
(when before
|
|
(funcall before args))
|
|
(setq retlist (multiple-value-list
|
|
(apply (or replace
|
|
saved) args)))
|
|
(setq completed t)
|
|
(values-list retlist))
|
|
(when after
|
|
(funcall after (if completed
|
|
retlist
|
|
:exited-non-locally))))))))
|
|
(setf (get spec 'slime-wrap) (list saved replacement))
|
|
(setf (symbol-function spec) replacement))
|
|
spec)
|
|
|
|
(definterface unwrap (spec indicator)
|
|
"Remove from SPEC any wrappings tagged with INDICATOR."
|
|
(if (wrapped-p spec indicator)
|
|
(setf (symbol-function spec) (first (get spec 'slime-wrap)))
|
|
(cerror "All right, so I did"
|
|
"Hmmm, ~a is not correctly wrapped, you probably redefined it"
|
|
spec))
|
|
(setf (get spec 'slime-wrap) nil)
|
|
spec)
|
|
|
|
(definterface wrapped-p (spec indicator)
|
|
"Returns true if SPEC is wrapped with INDICATOR."
|
|
(declare (ignore indicator))
|
|
(and (symbolp spec)
|
|
(let ((prop-value (get spec 'slime-wrap)))
|
|
(cond ((and prop-value
|
|
(not (eq (second prop-value)
|
|
(symbol-function spec))))
|
|
(warn "~a appears to be incorrectly wrapped" spec)
|
|
nil)
|
|
(prop-value t)
|
|
(t nil)))))
|