|
|
- ;;; swank-arglists.lisp --- arglist related code ??
- ;;
- ;; Authors: Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>
- ;; Tobias C. Rittweiler <tcr@freebits.de>
- ;; and others
- ;;
- ;; License: Public Domain
- ;;
-
- (in-package :swank)
-
- (eval-when (:compile-toplevel :load-toplevel :execute)
- (swank-require :swank-c-p-c))
-
- ;;;; Utilities
-
- (defun compose (&rest functions)
- "Compose FUNCTIONS right-associatively, returning a function"
- #'(lambda (x)
- (reduce #'funcall functions :initial-value x :from-end t)))
-
- (defun length= (seq n)
- "Test for whether SEQ contains N number of elements. I.e. it's equivalent
- to (= (LENGTH SEQ) N), but besides being more concise, it may also be more
- efficiently implemented."
- (etypecase seq
- (list (do ((i n (1- i))
- (list seq (cdr list)))
- ((or (<= i 0) (null list))
- (and (zerop i) (null list)))))
- (sequence (= (length seq) n))))
-
- (declaim (inline memq))
- (defun memq (item list)
- (member item list :test #'eq))
-
- (defun exactly-one-p (&rest values)
- "If exactly one value in VALUES is non-NIL, this value is returned.
- Otherwise NIL is returned."
- (let ((found nil))
- (dolist (v values)
- (when v (if found
- (return-from exactly-one-p nil)
- (setq found v))))
- found))
-
- (defun valid-operator-symbol-p (symbol)
- "Is SYMBOL the name of a function, a macro, or a special-operator?"
- (or (fboundp symbol)
- (macro-function symbol)
- (special-operator-p symbol)
- (member symbol '(declare declaim))))
-
- (defun function-exists-p (form)
- (and (valid-function-name-p form)
- (fboundp form)
- t))
-
- (defmacro multiple-value-or (&rest forms)
- (if (null forms)
- nil
- (let ((first (first forms))
- (rest (rest forms)))
- `(let* ((values (multiple-value-list ,first))
- (primary-value (first values)))
- (if primary-value
- (values-list values)
- (multiple-value-or ,@rest))))))
-
- (defun arglist-available-p (arglist)
- (not (eql arglist :not-available)))
-
- (defmacro with-available-arglist ((var &rest more-vars) form &body body)
- `(multiple-value-bind (,var ,@more-vars) ,form
- (if (eql ,var :not-available)
- :not-available
- (progn ,@body))))
-
-
- ;;;; Arglist Definition
-
- (defstruct (arglist (:conc-name arglist.) (:predicate arglist-p))
- provided-args ; list of the provided actual arguments
- required-args ; list of the required arguments
- optional-args ; list of the optional arguments
- key-p ; whether &key appeared
- keyword-args ; list of the keywords
- rest ; name of the &rest or &body argument (if any)
- body-p ; whether the rest argument is a &body
- allow-other-keys-p ; whether &allow-other-keys appeared
- aux-args ; list of &aux variables
- any-p ; whether &any appeared
- any-args ; list of &any arguments [*]
- known-junk ; &whole, &environment
- unknown-junk) ; unparsed stuff
-
- ;;;
- ;;; [*] The &ANY lambda keyword is an extension to ANSI Common Lisp,
- ;;; and is only used to describe certain arglists that cannot be
- ;;; described in another way.
- ;;;
- ;;; &ANY is very similiar to &KEY but while &KEY is based upon
- ;;; the idea of a plist (key1 value1 key2 value2), &ANY is a
- ;;; cross between &OPTIONAL, &KEY and *FEATURES* lists:
- ;;;
- ;;; a) (&ANY :A :B :C) means that you can provide any (non-null)
- ;;; set consisting of the keywords `:A', `:B', or `:C' in
- ;;; the arglist. E.g. (:A) or (:C :B :A).
- ;;;
- ;;; (This is not restricted to keywords only, but any self-evaluating
- ;;; expression is allowed.)
- ;;;
- ;;; b) (&ANY (key1 v1) (key2 v2) (key3 v3)) means that you can
- ;;; provide any (non-null) set consisting of lists where
- ;;; the CAR of the list is one of `key1', `key2', or `key3'.
- ;;; E.g. ((key1 100) (key3 42)), or ((key3 66) (key2 23))
- ;;;
- ;;;
- ;;; For example, a) let us describe the situations of EVAL-WHEN as
- ;;;
- ;;; (EVAL-WHEN (&ANY :compile-toplevel :load-toplevel :execute) &BODY body)
- ;;;
- ;;; and b) let us describe the optimization qualifiers that are valid
- ;;; in the declaration specifier `OPTIMIZE':
- ;;;
- ;;; (DECLARE (OPTIMIZE &ANY (compilation-speed 1) (safety 1) ...))
- ;;;
-
- ;; This is a wrapper object around anything that came from Slime and
- ;; could not reliably be read.
- (defstruct (arglist-dummy
- (:conc-name #:arglist-dummy.)
- (:constructor make-arglist-dummy (string-representation)))
- string-representation)
-
- (defun empty-arg-p (dummy)
- (and (arglist-dummy-p dummy)
- (zerop (length (arglist-dummy.string-representation dummy)))))
-
- (eval-when (:compile-toplevel :load-toplevel :execute)
- (defparameter +lambda-list-keywords+
- '(&provided &required &optional &rest &key &any)))
-
- (defmacro do-decoded-arglist (decoded-arglist &body clauses)
- (assert (loop for clause in clauses
- thereis (member (car clause) +lambda-list-keywords+)))
- (flet ((parse-clauses (clauses)
- (let* ((size (length +lambda-list-keywords+))
- (initial (make-hash-table :test #'eq :size size))
- (main (make-hash-table :test #'eq :size size))
- (final (make-hash-table :test #'eq :size size)))
- (loop for clause in clauses
- for lambda-list-keyword = (first clause)
- for clause-parameter = (second clause)
- do
- (case clause-parameter
- (:initially
- (setf (gethash lambda-list-keyword initial) clause))
- (:finally
- (setf (gethash lambda-list-keyword final) clause))
- (t
- (setf (gethash lambda-list-keyword main) clause)))
- finally
- (return (values initial main final)))))
- (generate-main-clause (clause arglist)
- (dcase clause
- ((&provided (&optional arg) . body)
- (let ((gensym (gensym "PROVIDED-ARG+")))
- `(dolist (,gensym (arglist.provided-args ,arglist))
- (declare (ignorable ,gensym))
- (let (,@(when arg `((,arg ,gensym))))
- ,@body))))
- ((&required (&optional arg) . body)
- (let ((gensym (gensym "REQUIRED-ARG+")))
- `(dolist (,gensym (arglist.required-args ,arglist))
- (declare (ignorable ,gensym))
- (let (,@(when arg `((,arg ,gensym))))
- ,@body))))
- ((&optional (&optional arg init) . body)
- (let ((optarg (gensym "OPTIONAL-ARG+")))
- `(dolist (,optarg (arglist.optional-args ,arglist))
- (declare (ignorable ,optarg))
- (let (,@(when arg
- `((,arg (optional-arg.arg-name ,optarg))))
- ,@(when init
- `((,init (optional-arg.default-arg ,optarg)))))
- ,@body))))
- ((&key (&optional keyword arg init) . body)
- (let ((keyarg (gensym "KEY-ARG+")))
- `(dolist (,keyarg (arglist.keyword-args ,arglist))
- (declare (ignorable ,keyarg))
- (let (,@(when keyword
- `((,keyword (keyword-arg.keyword ,keyarg))))
- ,@(when arg
- `((,arg (keyword-arg.arg-name ,keyarg))))
- ,@(when init
- `((,init (keyword-arg.default-arg ,keyarg)))))
- ,@body))))
- ((&rest (&optional arg body-p) . body)
- `(when (arglist.rest ,arglist)
- (let (,@(when arg `((,arg (arglist.rest ,arglist))))
- ,@(when body-p `((,body-p (arglist.body-p ,arglist)))))
- ,@body)))
- ((&any (&optional arg) . body)
- (let ((gensym (gensym "REQUIRED-ARG+")))
- `(dolist (,gensym (arglist.any-args ,arglist))
- (declare (ignorable ,gensym))
- (let (,@(when arg `((,arg ,gensym))))
- ,@body)))))))
- (let ((arglist (gensym "DECODED-ARGLIST+")))
- (multiple-value-bind (initially-clauses main-clauses finally-clauses)
- (parse-clauses clauses)
- `(let ((,arglist ,decoded-arglist))
- (block do-decoded-arglist
- ,@(loop for keyword in '(&provided &required
- &optional &rest &key &any)
- append (cddr (gethash keyword initially-clauses))
- collect (let ((clause (gethash keyword main-clauses)))
- (when clause
- (generate-main-clause clause arglist)))
- append (cddr (gethash keyword finally-clauses)))))))))
-
- ;;;; Arglist Printing
-
- (defun undummy (x)
- (if (typep x 'arglist-dummy)
- (arglist-dummy.string-representation x)
- (prin1-to-string x)))
-
- (defun print-decoded-arglist (arglist &key operator provided-args highlight)
- (let ((first-space-after-operator (and operator t)))
- (macrolet ((space ()
- ;; Kludge: When OPERATOR is not given, we don't want to
- ;; print a space for the first argument.
- `(if (not operator)
- (setq operator t)
- (progn (write-char #\space)
- (if first-space-after-operator
- (setq first-space-after-operator nil)
- (pprint-newline :fill)))))
- (with-highlighting ((&key index) &body body)
- `(if (eql ,index (car highlight))
- (progn (princ "===> ") ,@body (princ " <==="))
- (progn ,@body)))
- (print-arglist-recursively (argl &key index)
- `(if (eql ,index (car highlight))
- (print-decoded-arglist ,argl :highlight (cdr highlight))
- (print-decoded-arglist ,argl))))
- (let ((index 0))
- (pprint-logical-block (nil nil :prefix "(" :suffix ")")
- (when operator
- (print-arg operator)
- (pprint-indent :current 1)) ; 1 due to possibly added space
- (do-decoded-arglist (remove-given-args arglist provided-args)
- (&provided (arg)
- (space)
- (print-arg arg :literal-strings t)
- (incf index))
- (&required (arg)
- (space)
- (if (arglist-p arg)
- (print-arglist-recursively arg :index index)
- (with-highlighting (:index index)
- (print-arg arg)))
- (incf index))
- (&optional :initially
- (when (arglist.optional-args arglist)
- (space)
- (princ '&optional)))
- (&optional (arg init-value)
- (space)
- (if (arglist-p arg)
- (print-arglist-recursively arg :index index)
- (with-highlighting (:index index)
- (if (null init-value)
- (print-arg arg)
- (format t "~:@<~A ~A~@:>"
- (undummy arg) (undummy init-value)))))
- (incf index))
- (&key :initially
- (when (arglist.key-p arglist)
- (space)
- (princ '&key)))
- (&key (keyword arg init)
- (space)
- (if (arglist-p arg)
- (pprint-logical-block (nil nil :prefix "(" :suffix ")")
- (prin1 keyword) (space)
- (print-arglist-recursively arg :index keyword))
- (with-highlighting (:index keyword)
- (cond ((and init (keywordp keyword))
- (format t "~:@<~A ~A~@:>" keyword (undummy init)))
- (init
- (format t "~:@<(~A ..) ~A~@:>"
- (undummy keyword) (undummy init)))
- ((not (keywordp keyword))
- (format t "~:@<(~S ..)~@:>" keyword))
- (t
- (princ keyword))))))
- (&key :finally
- (when (arglist.allow-other-keys-p arglist)
- (space)
- (princ '&allow-other-keys)))
- (&any :initially
- (when (arglist.any-p arglist)
- (space)
- (princ '&any)))
- (&any (arg)
- (space)
- (print-arg arg))
- (&rest (args bodyp)
- (space)
- (princ (if bodyp '&body '&rest))
- (space)
- (if (arglist-p args)
- (print-arglist-recursively args :index index)
- (with-highlighting (:index index)
- (print-arg args))))
- ;; FIXME: add &UNKNOWN-JUNK?
- ))))))
-
- (defun print-arg (arg &key literal-strings)
- (let ((arg (if (arglist-dummy-p arg)
- (arglist-dummy.string-representation arg)
- arg)))
- (if (or
- (and literal-strings
- (stringp arg))
- (keywordp arg))
- (prin1 arg)
- (princ arg))))
-
- (defun print-decoded-arglist-as-template (decoded-arglist &key
- (prefix "(") (suffix ")"))
- (let ((first-p t))
- (flet ((space ()
- (unless first-p
- (write-char #\space))
- (setq first-p nil))
- (print-arg-or-pattern (arg)
- (etypecase arg
- (symbol (if (keywordp arg) (prin1 arg) (princ arg)))
- (string (princ arg))
- (list (princ arg))
- (arglist-dummy (princ
- (arglist-dummy.string-representation arg)))
- (arglist (print-decoded-arglist-as-template arg)))
- (pprint-newline :fill)))
- (pprint-logical-block (nil nil :prefix prefix :suffix suffix)
- (do-decoded-arglist decoded-arglist
- (&provided ()) ; do nothing; provided args are in the buffer already.
- (&required (arg)
- (space) (print-arg-or-pattern arg))
- (&optional (arg)
- (space) (princ "[") (print-arg-or-pattern arg) (princ "]"))
- (&key (keyword arg)
- (space)
- (prin1 (if (keywordp keyword) keyword `',keyword))
- (space)
- (print-arg-or-pattern arg)
- (pprint-newline :linear))
- (&any (arg)
- (space) (print-arg-or-pattern arg))
- (&rest (args)
- (when (or (not (arglist.keyword-args decoded-arglist))
- (arglist.allow-other-keys-p decoded-arglist))
- (space)
- (format t "~A..." args))))))))
-
- (defvar *arglist-pprint-bindings*
- '((*print-case* . :downcase)
- (*print-pretty* . t)
- (*print-circle* . nil)
- (*print-readably* . nil)
- (*print-level* . 10)
- (*print-length* . 20)
- (*print-escape* . nil)))
-
- (defvar *arglist-show-packages* t)
-
- (defmacro with-arglist-io-syntax (&body body)
- (let ((package (gensym)))
- `(let ((,package *package*))
- (with-standard-io-syntax
- (let ((*package* (if *arglist-show-packages*
- *package*
- ,package)))
- (with-bindings *arglist-pprint-bindings*
- ,@body))))))
-
- (defun decoded-arglist-to-string (decoded-arglist
- &key operator highlight
- print-right-margin)
- (with-output-to-string (*standard-output*)
- (with-arglist-io-syntax
- (let ((*print-right-margin* print-right-margin))
- (print-decoded-arglist decoded-arglist
- :operator operator
- :highlight highlight)))))
-
- (defun decoded-arglist-to-template-string (decoded-arglist
- &key (prefix "(") (suffix ")"))
- (with-output-to-string (*standard-output*)
- (with-arglist-io-syntax
- (print-decoded-arglist-as-template decoded-arglist
- :prefix prefix
- :suffix suffix))))
-
- ;;;; Arglist Decoding / Encoding
-
- (defun decode-required-arg (arg)
- "ARG can be a symbol or a destructuring pattern."
- (etypecase arg
- (symbol arg)
- (arglist-dummy arg)
- (list (decode-arglist arg))))
-
- (defun encode-required-arg (arg)
- (etypecase arg
- (symbol arg)
- (arglist (encode-arglist arg))))
-
- (defstruct (keyword-arg
- (:conc-name keyword-arg.)
- (:constructor %make-keyword-arg))
- keyword
- arg-name
- default-arg)
-
- (defun canonicalize-default-arg (form)
- (if (equalp ''nil form)
- nil
- form))
-
- (defun make-keyword-arg (keyword arg-name default-arg)
- (%make-keyword-arg :keyword keyword
- :arg-name arg-name
- :default-arg (canonicalize-default-arg default-arg)))
-
- (defun decode-keyword-arg (arg)
- "Decode a keyword item of formal argument list.
- Return three values: keyword, argument name, default arg."
- (flet ((intern-as-keyword (arg)
- (intern (etypecase arg
- (symbol (symbol-name arg))
- (arglist-dummy (arglist-dummy.string-representation arg)))
- keyword-package)))
- (cond ((or (symbolp arg) (arglist-dummy-p arg))
- (make-keyword-arg (intern-as-keyword arg) arg nil))
- ((and (consp arg)
- (consp (car arg)))
- (make-keyword-arg (caar arg)
- (decode-required-arg (cadar arg))
- (cadr arg)))
- ((consp arg)
- (make-keyword-arg (intern-as-keyword (car arg))
- (car arg) (cadr arg)))
- (t
- (error "Bad keyword item of formal argument list")))))
-
- (defun encode-keyword-arg (arg)
- (cond
- ((arglist-p (keyword-arg.arg-name arg))
- ;; Destructuring pattern
- (let ((keyword/name (list (keyword-arg.keyword arg)
- (encode-required-arg
- (keyword-arg.arg-name arg)))))
- (if (keyword-arg.default-arg arg)
- (list keyword/name
- (keyword-arg.default-arg arg))
- (list keyword/name))))
- ((eql (intern (symbol-name (keyword-arg.arg-name arg))
- keyword-package)
- (keyword-arg.keyword arg))
- (if (keyword-arg.default-arg arg)
- (list (keyword-arg.arg-name arg)
- (keyword-arg.default-arg arg))
- (keyword-arg.arg-name arg)))
- (t
- (let ((keyword/name (list (keyword-arg.keyword arg)
- (keyword-arg.arg-name arg))))
- (if (keyword-arg.default-arg arg)
- (list keyword/name
- (keyword-arg.default-arg arg))
- (list keyword/name))))))
-
- (progn
- (assert (equalp (decode-keyword-arg 'x)
- (make-keyword-arg :x 'x nil)))
- (assert (equalp (decode-keyword-arg '(x t))
- (make-keyword-arg :x 'x t)))
- (assert (equalp (decode-keyword-arg '((:x y)))
- (make-keyword-arg :x 'y nil)))
- (assert (equalp (decode-keyword-arg '((:x y) t))
- (make-keyword-arg :x 'y t))))
-
- ;;; FIXME suppliedp?
- (defstruct (optional-arg
- (:conc-name optional-arg.)
- (:constructor %make-optional-arg))
- arg-name
- default-arg)
-
- (defun make-optional-arg (arg-name default-arg)
- (%make-optional-arg :arg-name arg-name
- :default-arg (canonicalize-default-arg default-arg)))
-
- (defun decode-optional-arg (arg)
- "Decode an optional item of a formal argument list.
- Return an OPTIONAL-ARG structure."
- (etypecase arg
- (symbol (make-optional-arg arg nil))
- (arglist-dummy (make-optional-arg arg nil))
- (list (make-optional-arg (decode-required-arg (car arg))
- (cadr arg)))))
-
- (defun encode-optional-arg (optional-arg)
- (if (or (optional-arg.default-arg optional-arg)
- (arglist-p (optional-arg.arg-name optional-arg)))
- (list (encode-required-arg
- (optional-arg.arg-name optional-arg))
- (optional-arg.default-arg optional-arg))
- (optional-arg.arg-name optional-arg)))
-
- (progn
- (assert (equalp (decode-optional-arg 'x)
- (make-optional-arg 'x nil)))
- (assert (equalp (decode-optional-arg '(x t))
- (make-optional-arg 'x t))))
-
- (define-modify-macro nreversef () nreverse "Reverse the list in PLACE.")
-
- (defun decode-arglist (arglist)
- "Parse the list ARGLIST and return an ARGLIST structure."
- (etypecase arglist
- ((eql :not-available) (return-from decode-arglist
- :not-available))
- (list))
- (loop
- with mode = nil
- with result = (make-arglist)
- for arg = (if (consp arglist)
- (pop arglist)
- (progn
- (prog1 arglist
- (setf mode '&rest
- arglist nil))))
- do (cond
- ((eql mode '&unknown-junk)
- ;; don't leave this mode -- we don't know how the arglist
- ;; after unknown lambda-list keywords is interpreted
- (push arg (arglist.unknown-junk result)))
- ((eql arg '&allow-other-keys)
- (setf (arglist.allow-other-keys-p result) t))
- ((eql arg '&key)
- (setf (arglist.key-p result) t
- mode arg))
- ((memq arg '(&optional &rest &body &aux))
- (setq mode arg))
- ((memq arg '(&whole &environment))
- (setq mode arg)
- (push arg (arglist.known-junk result)))
- ((and (symbolp arg)
- (string= (symbol-name arg) (string '#:&any))) ; may be interned
- (setf (arglist.any-p result) t) ; in any *package*.
- (setq mode '&any))
- ((memq arg lambda-list-keywords)
- (setq mode '&unknown-junk)
- (push arg (arglist.unknown-junk result)))
- (t
- (ecase mode
- (&key
- (push (decode-keyword-arg arg)
- (arglist.keyword-args result)))
- (&optional
- (push (decode-optional-arg arg)
- (arglist.optional-args result)))
- (&body
- (setf (arglist.body-p result) t
- (arglist.rest result) arg))
- (&rest
- (setf (arglist.rest result) arg))
- (&aux
- (push (decode-optional-arg arg)
- (arglist.aux-args result)))
- ((nil)
- (push (decode-required-arg arg)
- (arglist.required-args result)))
- ((&whole &environment)
- (setf mode nil)
- (push arg (arglist.known-junk result)))
- (&any
- (push arg (arglist.any-args result))))))
- until (null arglist)
- finally (nreversef (arglist.required-args result))
- finally (nreversef (arglist.optional-args result))
- finally (nreversef (arglist.keyword-args result))
- finally (nreversef (arglist.aux-args result))
- finally (nreversef (arglist.any-args result))
- finally (nreversef (arglist.known-junk result))
- finally (nreversef (arglist.unknown-junk result))
- finally (assert (or (and (not (arglist.key-p result))
- (not (arglist.any-p result)))
- (exactly-one-p (arglist.key-p result)
- (arglist.any-p result))))
- finally (return result)))
-
- (defun encode-arglist (decoded-arglist)
- (append (mapcar #'encode-required-arg
- (arglist.required-args decoded-arglist))
- (when (arglist.optional-args decoded-arglist)
- '(&optional))
- (mapcar #'encode-optional-arg
- (arglist.optional-args decoded-arglist))
- (when (arglist.key-p decoded-arglist)
- '(&key))
- (mapcar #'encode-keyword-arg
- (arglist.keyword-args decoded-arglist))
- (when (arglist.allow-other-keys-p decoded-arglist)
- '(&allow-other-keys))
- (when (arglist.any-args decoded-arglist)
- `(&any ,@(arglist.any-args decoded-arglist)))
- (cond ((not (arglist.rest decoded-arglist))
- '())
- ((arglist.body-p decoded-arglist)
- `(&body ,(arglist.rest decoded-arglist)))
- (t
- `(&rest ,(arglist.rest decoded-arglist))))
- (when (arglist.aux-args decoded-arglist)
- `(&aux ,(arglist.aux-args decoded-arglist)))
- (arglist.known-junk decoded-arglist)
- (arglist.unknown-junk decoded-arglist)))
-
- ;;;; Arglist Enrichment
-
- (defun arglist-keywords (lambda-list)
- "Return the list of keywords in ARGLIST.
- As a secondary value, return whether &allow-other-keys appears."
- (let ((decoded-arglist (decode-arglist lambda-list)))
- (values (arglist.keyword-args decoded-arglist)
- (arglist.allow-other-keys-p decoded-arglist))))
-
-
- (defun methods-keywords (methods)
- "Collect all keywords in the arglists of METHODS.
- As a secondary value, return whether &allow-other-keys appears somewhere."
- (let ((keywords '())
- (allow-other-keys nil))
- (dolist (method methods)
- (multiple-value-bind (kw aok)
- (arglist-keywords
- (swank-mop:method-lambda-list method))
- (setq keywords (remove-duplicates (append keywords kw)
- :key #'keyword-arg.keyword)
- allow-other-keys (or allow-other-keys aok))))
- (values keywords allow-other-keys)))
-
- (defun generic-function-keywords (generic-function)
- "Collect all keywords in the methods of GENERIC-FUNCTION.
- As a secondary value, return whether &allow-other-keys appears somewhere."
- (methods-keywords
- (swank-mop:generic-function-methods generic-function)))
-
- (defun applicable-methods-keywords (generic-function arguments)
- "Collect all keywords in the methods of GENERIC-FUNCTION that are
- applicable for argument of CLASSES. As a secondary value, return
- whether &allow-other-keys appears somewhere."
- (methods-keywords
- (multiple-value-bind (amuc okp)
- (swank-mop:compute-applicable-methods-using-classes
- generic-function (mapcar #'class-of arguments))
- (if okp
- amuc
- (compute-applicable-methods generic-function arguments)))))
-
- (defgeneric extra-keywords (operator args)
- (:documentation "Return a list of extra keywords of OPERATOR (a
- symbol) when applied to the (unevaluated) ARGS.
- As a secondary value, return whether other keys are allowed.
- As a tertiary value, return the initial sublist of ARGS that was needed
- to determine the extra keywords."))
-
- ;;; We make sure that symbol-from-KEYWORD-using keywords come before
- ;;; symbol-from-arbitrary-package-using keywords. And we sort the
- ;;; latter according to how their home-packages relate to *PACKAGE*.
- ;;;
- ;;; Rationale is to show those key parameters first which make most
- ;;; sense in the current context. And in particular: to put
- ;;; implementation-internal stuff last.
- ;;;
- ;;; This matters tremendeously on Allegro in combination with
- ;;; AllegroCache as that does some evil tinkering with initargs,
- ;;; obfuscating the arglist of MAKE-INSTANCE.
- ;;;
-
- (defmethod extra-keywords :around (op args)
- (declare (ignorable op args))
- (multiple-value-bind (keywords aok enrichments) (call-next-method)
- (values (sort-extra-keywords keywords) aok enrichments)))
-
- (defun make-package-comparator (reference-packages)
- "Returns a two-argument test function which compares packages
- according to their used-by relation with REFERENCE-PACKAGES. Packages
- will be sorted first which appear first in the PACKAGE-USE-LIST of the
- reference packages."
- (let ((package-use-table (make-hash-table :test 'eq)))
- ;; Walk the package dependency graph breadth-fist, and fill
- ;; PACKAGE-USE-TABLE accordingly.
- (loop with queue = (copy-list reference-packages)
- with bfn = 0 ; Breadth-First Number
- for p = (pop queue)
- unless (gethash p package-use-table)
- do (setf (gethash p package-use-table) (shiftf bfn (1+ bfn)))
- and do (setf queue (nconc queue (copy-list (package-use-list p))))
- while queue)
- #'(lambda (p1 p2)
- (let ((bfn1 (gethash p1 package-use-table))
- (bfn2 (gethash p2 package-use-table)))
- (cond ((and bfn1 bfn2) (<= bfn1 bfn2))
- (bfn1 bfn1)
- (bfn2 nil) ; p2 is used, p1 not
- (t (string<= (package-name p1) (package-name p2))))))))
-
- (defun sort-extra-keywords (kwds)
- (stable-sort kwds (make-package-comparator (list keyword-package *package*))
- :key (compose #'symbol-package #'keyword-arg.keyword)))
-
- (defun keywords-of-operator (operator)
- "Return a list of KEYWORD-ARGs that OPERATOR accepts.
- This function is useful for writing EXTRA-KEYWORDS methods for
- user-defined functions which are declared &ALLOW-OTHER-KEYS and which
- forward keywords to OPERATOR."
- (with-available-arglist (arglist) (arglist-from-form (ensure-list operator))
- (values (arglist.keyword-args arglist)
- (arglist.allow-other-keys-p arglist))))
-
- (defmethod extra-keywords (operator args)
- ;; default method
- (declare (ignore args))
- (let ((symbol-function (symbol-function operator)))
- (if (typep symbol-function 'generic-function)
- (generic-function-keywords symbol-function)
- nil)))
-
- (defun class-from-class-name-form (class-name-form)
- (when (and (listp class-name-form)
- (= (length class-name-form) 2)
- (eq (car class-name-form) 'quote))
- (let* ((class-name (cadr class-name-form))
- (class (find-class class-name nil)))
- (when (and class
- (not (swank-mop:class-finalized-p class)))
- ;; Try to finalize the class, which can fail if
- ;; superclasses are not defined yet
- (ignore-errors (swank-mop:finalize-inheritance class)))
- class)))
-
- (defun extra-keywords/slots (class)
- (multiple-value-bind (slots allow-other-keys-p)
- (if (swank-mop:class-finalized-p class)
- (values (swank-mop:class-slots class) nil)
- (values (swank-mop:class-direct-slots class) t))
- (let ((slot-init-keywords
- (loop for slot in slots append
- (mapcar (lambda (initarg)
- (make-keyword-arg
- initarg
- (swank-mop:slot-definition-name slot)
- (and (swank-mop:slot-definition-initfunction slot)
- (swank-mop:slot-definition-initform slot))))
- (swank-mop:slot-definition-initargs slot)))))
- (values slot-init-keywords allow-other-keys-p))))
-
- (defun extra-keywords/make-instance (operator args)
- (declare (ignore operator))
- (unless (null args)
- (let* ((class-name-form (car args))
- (class (class-from-class-name-form class-name-form)))
- (when class
- (multiple-value-bind (slot-init-keywords class-aokp)
- (extra-keywords/slots class)
- (multiple-value-bind (allocate-instance-keywords ai-aokp)
- (applicable-methods-keywords
- #'allocate-instance (list class))
- (multiple-value-bind (initialize-instance-keywords ii-aokp)
- (ignore-errors
- (applicable-methods-keywords
- #'initialize-instance
- (list (swank-mop:class-prototype class))))
- (multiple-value-bind (shared-initialize-keywords si-aokp)
- (ignore-errors
- (applicable-methods-keywords
- #'shared-initialize
- (list (swank-mop:class-prototype class) t)))
- (values (append slot-init-keywords
- allocate-instance-keywords
- initialize-instance-keywords
- shared-initialize-keywords)
- (or class-aokp ai-aokp ii-aokp si-aokp)
- (list class-name-form))))))))))
-
- (defun extra-keywords/change-class (operator args)
- (declare (ignore operator))
- (unless (null args)
- (let* ((class-name-form (car args))
- (class (class-from-class-name-form class-name-form)))
- (when class
- (multiple-value-bind (slot-init-keywords class-aokp)
- (extra-keywords/slots class)
- (declare (ignore class-aokp))
- (multiple-value-bind (shared-initialize-keywords si-aokp)
- (ignore-errors
- (applicable-methods-keywords
- #'shared-initialize
- (list (swank-mop:class-prototype class) t)))
- ;; FIXME: much as it would be nice to include the
- ;; applicable keywords from
- ;; UPDATE-INSTANCE-FOR-DIFFERENT-CLASS, I don't really see
- ;; how to do it: so we punt, always declaring
- ;; &ALLOW-OTHER-KEYS.
- (declare (ignore si-aokp))
- (values (append slot-init-keywords shared-initialize-keywords)
- t
- (list class-name-form))))))))
-
- (defmethod extra-keywords ((operator (eql 'make-instance))
- args)
- (multiple-value-or (extra-keywords/make-instance operator args)
- (call-next-method)))
-
- (defmethod extra-keywords ((operator (eql 'make-condition))
- args)
- (multiple-value-or (extra-keywords/make-instance operator args)
- (call-next-method)))
-
- (defmethod extra-keywords ((operator (eql 'error))
- args)
- (multiple-value-or (extra-keywords/make-instance operator args)
- (call-next-method)))
-
- (defmethod extra-keywords ((operator (eql 'signal))
- args)
- (multiple-value-or (extra-keywords/make-instance operator args)
- (call-next-method)))
-
- (defmethod extra-keywords ((operator (eql 'warn))
- args)
- (multiple-value-or (extra-keywords/make-instance operator args)
- (call-next-method)))
-
- (defmethod extra-keywords ((operator (eql 'cerror))
- args)
- (multiple-value-bind (keywords aok determiners)
- (extra-keywords/make-instance operator (cdr args))
- (if keywords
- (values keywords aok
- (cons (car args) determiners))
- (call-next-method))))
-
- (defmethod extra-keywords ((operator (eql 'change-class))
- args)
- (multiple-value-bind (keywords aok determiners)
- (extra-keywords/change-class operator (cdr args))
- (if keywords
- (values keywords aok
- (cons (car args) determiners))
- (call-next-method))))
-
- (defun enrich-decoded-arglist-with-keywords (decoded-arglist keywords
- allow-other-keys-p)
- "Modify DECODED-ARGLIST using KEYWORDS and ALLOW-OTHER-KEYS-P."
- (when keywords
- (setf (arglist.key-p decoded-arglist) t)
- (setf (arglist.keyword-args decoded-arglist)
- (remove-duplicates
- (append (arglist.keyword-args decoded-arglist)
- keywords)
- :key #'keyword-arg.keyword)))
- (setf (arglist.allow-other-keys-p decoded-arglist)
- (or (arglist.allow-other-keys-p decoded-arglist)
- allow-other-keys-p)))
-
- (defun enrich-decoded-arglist-with-extra-keywords (decoded-arglist form)
- "Determine extra keywords from the function call FORM, and modify
- DECODED-ARGLIST to include them. As a secondary return value, return
- the initial sublist of ARGS that was needed to determine the extra
- keywords. As a tertiary return value, return whether any enrichment
- was done."
- (multiple-value-bind (extra-keywords extra-aok determining-args)
- (extra-keywords (car form) (cdr form))
- ;; enrich the list of keywords with the extra keywords
- (enrich-decoded-arglist-with-keywords decoded-arglist
- extra-keywords extra-aok)
- (values decoded-arglist
- determining-args
- (or extra-keywords extra-aok))))
-
- (defgeneric compute-enriched-decoded-arglist (operator-form argument-forms)
- (:documentation
- "Return three values: DECODED-ARGLIST, DETERMINING-ARGS, and
- ANY-ENRICHMENT, just like enrich-decoded-arglist-with-extra-keywords.
- If the arglist is not available, return :NOT-AVAILABLE."))
-
- (defmethod compute-enriched-decoded-arglist (operator-form argument-forms)
- (with-available-arglist (decoded-arglist)
- (decode-arglist (arglist operator-form))
- (enrich-decoded-arglist-with-extra-keywords decoded-arglist
- (cons operator-form
- argument-forms))))
-
- (defmethod compute-enriched-decoded-arglist
- ((operator-form (eql 'with-open-file)) argument-forms)
- (declare (ignore argument-forms))
- (multiple-value-bind (decoded-arglist determining-args)
- (call-next-method)
- (let ((first-arg (first (arglist.required-args decoded-arglist)))
- (open-arglist (compute-enriched-decoded-arglist 'open nil)))
- (when (and (arglist-p first-arg) (arglist-p open-arglist))
- (enrich-decoded-arglist-with-keywords
- first-arg
- (arglist.keyword-args open-arglist)
- nil)))
- (values decoded-arglist determining-args t)))
-
- (defmethod compute-enriched-decoded-arglist ((operator-form (eql 'apply))
- argument-forms)
- (let ((function-name-form (car argument-forms)))
- (when (and (listp function-name-form)
- (length= function-name-form 2)
- (memq (car function-name-form) '(quote function)))
- (let ((function-name (cadr function-name-form)))
- (when (valid-operator-symbol-p function-name)
- (let ((function-arglist
- (compute-enriched-decoded-arglist function-name
- (cdr argument-forms))))
- (return-from compute-enriched-decoded-arglist
- (values
- (make-arglist :required-args
- (list 'function)
- :optional-args
- (append
- (mapcar #'(lambda (arg)
- (make-optional-arg arg nil))
- (arglist.required-args function-arglist))
- (arglist.optional-args function-arglist))
- :key-p
- (arglist.key-p function-arglist)
- :keyword-args
- (arglist.keyword-args function-arglist)
- :rest
- 'args
- :allow-other-keys-p
- (arglist.allow-other-keys-p function-arglist))
- (list function-name-form)
- t)))))))
- (call-next-method))
-
- (defmethod compute-enriched-decoded-arglist
- ((operator-form (eql 'multiple-value-call)) argument-forms)
- (compute-enriched-decoded-arglist 'apply argument-forms))
-
- (defun delete-given-args (decoded-arglist args)
- "Delete given ARGS from DECODED-ARGLIST."
- (macrolet ((pop-or-return (list)
- `(if (null ,list)
- (return-from do-decoded-arglist)
- (pop ,list))))
- (do-decoded-arglist decoded-arglist
- (&provided ()
- (assert (eq (pop-or-return args)
- (pop (arglist.provided-args decoded-arglist)))))
- (&required ()
- (pop-or-return args)
- (pop (arglist.required-args decoded-arglist)))
- (&optional ()
- (pop-or-return args)
- (pop (arglist.optional-args decoded-arglist)))
- (&key (keyword)
- ;; N.b. we consider a keyword to be given only when the keyword
- ;; _and_ a value has been given for it.
- (loop for (key value) on args by #'cddr
- when (and (eq keyword key) value)
- do (setf (arglist.keyword-args decoded-arglist)
- (remove keyword (arglist.keyword-args decoded-arglist)
- :key #'keyword-arg.keyword))))))
- decoded-arglist)
-
- (defun remove-given-args (decoded-arglist args)
- ;; FIXME: We actually needa deep copy here.
- (delete-given-args (copy-arglist decoded-arglist) args))
-
- ;;;; Arglist Retrieval
-
- (defun arglist-from-form (form)
- (if (null form)
- :not-available
- (arglist-dispatch (car form) (cdr form))))
-
- (export 'arglist-dispatch)
- (defgeneric arglist-dispatch (operator arguments)
- ;; Default method
- (:method (operator arguments)
- (unless (and (symbolp operator) (valid-operator-symbol-p operator))
- (return-from arglist-dispatch :not-available))
- (when (equalp (package-name (symbol-package operator)) "closer-mop")
- (let ((standard-symbol (or (find-symbol (symbol-name operator) :cl)
- (find-symbol (symbol-name operator) :swank-mop))))
- (when standard-symbol
- (return-from arglist-dispatch
- (arglist-dispatch standard-symbol arguments)))))
-
- (multiple-value-bind (decoded-arglist determining-args)
- (compute-enriched-decoded-arglist operator arguments)
- (with-available-arglist (arglist) decoded-arglist
- ;; replace some formal args by determining actual args
- (setf arglist (delete-given-args arglist determining-args))
- (setf (arglist.provided-args arglist) determining-args)
- arglist))))
-
- (defmethod arglist-dispatch ((operator (eql 'defmethod)) arguments)
- (match (cons operator arguments)
- (('defmethod (#'function-exists-p gf-name) . rest)
- (let ((gf (fdefinition gf-name)))
- (when (typep gf 'generic-function)
- (with-available-arglist (arglist) (decode-arglist (arglist gf))
- (let ((qualifiers (loop for x in rest
- until (or (listp x) (empty-arg-p x))
- collect x)))
- (return-from arglist-dispatch
- (make-arglist :provided-args (cons gf-name qualifiers)
- :required-args (list arglist)
- :rest "body" :body-p t)))))))
- (_)) ; Fall through
- (call-next-method))
-
- (defmethod arglist-dispatch ((operator (eql 'define-compiler-macro)) arguments)
- (match (cons operator arguments)
- (('define-compiler-macro (#'function-exists-p gf-name) . _)
- (let ((gf (fdefinition gf-name)))
- (with-available-arglist (arglist) (decode-arglist (arglist gf))
- (return-from arglist-dispatch
- (make-arglist :provided-args (list gf-name)
- :required-args (list arglist)
- :rest "body" :body-p t)))))
- (_)) ; Fall through
- (call-next-method))
-
-
- (defmethod arglist-dispatch ((operator (eql 'eval-when)) arguments)
- (declare (ignore arguments))
- (let ((eval-when-args '(:compile-toplevel :load-toplevel :execute)))
- (make-arglist
- :required-args (list (make-arglist :any-p t :any-args eval-when-args))
- :rest '#:body :body-p t)))
-
-
- (defmethod arglist-dispatch ((operator (eql 'declare)) arguments)
- (let* ((declaration (cons operator (last arguments)))
- (typedecl-arglist (arglist-for-type-declaration declaration)))
- (if (arglist-available-p typedecl-arglist)
- typedecl-arglist
- (match declaration
- (('declare ((#'consp typespec) . decl-args))
- (with-available-arglist (typespec-arglist)
- (decoded-arglist-for-type-specifier typespec)
- (make-arglist
- :required-args (list (make-arglist
- :required-args (list typespec-arglist)
- :rest '#:variables)))))
- (('declare (decl-identifier . decl-args))
- (decoded-arglist-for-declaration decl-identifier decl-args))
- (_ (make-arglist :rest '#:declaration-specifiers))))))
-
- (defmethod arglist-dispatch ((operator (eql 'declaim)) arguments)
- (arglist-dispatch 'declare arguments))
-
-
- (defun arglist-for-type-declaration (declaration)
- (flet ((%arglist-for-type-declaration (identifier typespec rest-var-name)
- (with-available-arglist (typespec-arglist)
- (decoded-arglist-for-type-specifier typespec)
- (make-arglist
- :required-args (list (make-arglist
- :provided-args (list identifier)
- :required-args (list typespec-arglist)
- :rest rest-var-name))))))
- (match declaration
- (('declare ('type (#'consp typespec) . decl-args))
- (%arglist-for-type-declaration 'type typespec '#:variables))
- (('declare ('ftype (#'consp typespec) . decl-args))
- (%arglist-for-type-declaration 'ftype typespec '#:function-names))
- (('declare ((#'consp typespec) . decl-args))
- (with-available-arglist (typespec-arglist)
- (decoded-arglist-for-type-specifier typespec)
- (make-arglist
- :required-args (list (make-arglist
- :required-args (list typespec-arglist)
- :rest '#:variables)))))
- (_ :not-available))))
-
- (defun decoded-arglist-for-declaration (decl-identifier decl-args)
- (declare (ignore decl-args))
- (with-available-arglist (arglist)
- (decode-arglist (declaration-arglist decl-identifier))
- (setf (arglist.provided-args arglist) (list decl-identifier))
- (make-arglist :required-args (list arglist))))
-
- (defun decoded-arglist-for-type-specifier (type-specifier)
- (etypecase type-specifier
- (arglist-dummy :not-available)
- (cons (decoded-arglist-for-type-specifier (car type-specifier)))
- (symbol
- (with-available-arglist (arglist)
- (decode-arglist (type-specifier-arglist type-specifier))
- (setf (arglist.provided-args arglist) (list type-specifier))
- arglist))))
-
- ;;; Slimefuns
-
- ;;; We work on a RAW-FORM, or BUFFER-FORM, which represent the form at
- ;;; user's point in Emacs. A RAW-FORM looks like
- ;;;
- ;;; ("FOO" ("BAR" ...) "QUUX" ("ZURP" SWANK::%CURSOR-MARKER%))
- ;;;
- ;;; The expression before the cursor marker is the expression where
- ;;; user's cursor points at. An explicit marker is necessary to
- ;;; disambiguate between
- ;;;
- ;;; ("IF" ("PRED")
- ;;; ("F" "X" "Y" %CURSOR-MARKER%))
- ;;;
- ;;; and
- ;;; ("IF" ("PRED")
- ;;; ("F" "X" "Y") %CURSOR-MARKER%)
-
- ;;; Notice that for a form like (FOO (BAR |) QUUX), where | denotes
- ;;; user's point, the following should be sent ("FOO" ("BAR" ""
- ;;; %CURSOR-MARKER%)). Only the forms up to point should be
- ;;; considered.
-
- (defslimefun autodoc (raw-form &key print-right-margin)
- "Return a list of two elements.
- First, a string representing the arglist for the deepest subform in
- RAW-FORM that does have an arglist. The highlighted parameter is
- wrapped in ===> X <===.
-
- Second, a boolean value telling whether the returned string can be cached."
- (handler-bind ((serious-condition
- #'(lambda (c)
- (unless (debug-on-swank-error)
- (let ((*print-right-margin* print-right-margin))
- (return-from autodoc
- (format nil "Arglist Error: \"~A\"" c)))))))
- (with-buffer-syntax ()
- (multiple-value-bind (form arglist obj-at-cursor form-path)
- (find-subform-with-arglist (parse-raw-form raw-form))
- (cond ((boundp-and-interesting obj-at-cursor)
- (list (print-variable-to-string obj-at-cursor) nil))
- (t
- (list
- (with-available-arglist (arglist) arglist
- (decoded-arglist-to-string
- arglist
- :print-right-margin print-right-margin
- :operator (car form)
- :highlight (form-path-to-arglist-path form-path
- form
- arglist)))
- t)))))))
-
- (defun boundp-and-interesting (symbol)
- (and symbol
- (symbolp symbol)
- (boundp symbol)
- (not (memq symbol '(cl:t cl:nil)))
- (not (keywordp symbol))))
-
- (defun print-variable-to-string (symbol)
- "Return a short description of VARIABLE-NAME, or NIL."
- (let ((*print-pretty* t) (*print-level* 4)
- (*print-length* 10) (*print-lines* 1)
- (*print-readably* nil)
- (value (symbol-value symbol)))
- (call/truncated-output-to-string
- 75 (lambda (s)
- (without-printing-errors (:object value :stream s)
- (format s "~A ~A~S" symbol *echo-area-prefix* value))))))
-
-
- (defslimefun complete-form (raw-form)
- "Read FORM-STRING in the current buffer package, then complete it
- by adding a template for the missing arguments."
- ;; We do not catch errors here because COMPLETE-FORM is an
- ;; interactive command, not automatically run in the background like
- ;; ARGLIST-FOR-ECHO-AREA.
- (with-buffer-syntax ()
- (multiple-value-bind (arglist provided-args)
- (find-immediately-containing-arglist (parse-raw-form raw-form))
- (with-available-arglist (arglist) arglist
- (decoded-arglist-to-template-string
- (delete-given-args arglist
- (remove-if #'empty-arg-p provided-args
- :from-end t :count 1))
- :prefix "" :suffix "")))))
-
- (defslimefun completions-for-keyword (keyword-string raw-form)
- "Return a list of possible completions for KEYWORD-STRING relative
- to the context provided by RAW-FORM."
- (with-buffer-syntax ()
- (let ((arglist (find-immediately-containing-arglist
- (parse-raw-form raw-form))))
- (when (arglist-available-p arglist)
- ;; It would be possible to complete keywords only if we are in
- ;; a keyword position, but it is not clear if we want that.
- (let* ((keywords
- (append (mapcar #'keyword-arg.keyword
- (arglist.keyword-args arglist))
- (remove-if-not #'keywordp (arglist.any-args arglist))))
- (keyword-name
- (tokenize-symbol keyword-string))
- (matching-keywords
- (find-matching-symbols-in-list
- keyword-name keywords (make-compound-prefix-matcher #\-)))
- (converter (completion-output-symbol-converter keyword-string))
- (strings
- (mapcar converter
- (mapcar #'symbol-name matching-keywords)))
- (completion-set
- (format-completion-set strings nil "")))
- (list completion-set
- (longest-compound-prefix completion-set)))))))
-
- (defparameter +cursor-marker+ '%cursor-marker%)
-
- (defun find-subform-with-arglist (form)
- "Returns four values:
-
- The appropriate subform of `form' which is closest to the
- +CURSOR-MARKER+ and whose operator is valid and has an
- arglist. The +CURSOR-MARKER+ is removed from that subform.
-
- Second value is the arglist. Local function and macro definitions
- appearing in `form' into account.
-
- Third value is the object in front of +CURSOR-MARKER+.
-
- Fourth value is a form path to that object."
- (labels
- ((yield-success (form local-ops)
- (multiple-value-bind (form obj-at-cursor form-path)
- (extract-cursor-marker form)
- (values form
- (let ((entry (assoc (car form) local-ops :test #'op=)))
- (if entry
- (decode-arglist (cdr entry))
- (arglist-from-form form)))
- obj-at-cursor
- form-path)))
- (yield-failure ()
- (values nil :not-available))
- (operator-p (operator local-ops)
- (or (and (symbolp operator) (valid-operator-symbol-p operator))
- (assoc operator local-ops :test #'op=)))
- (op= (op1 op2)
- (cond ((and (symbolp op1) (symbolp op2))
- (eq op1 op2))
- ((and (arglist-dummy-p op1) (arglist-dummy-p op2))
- (string= (arglist-dummy.string-representation op1)
- (arglist-dummy.string-representation op2)))))
- (grovel-form (form local-ops)
- "Descend FORM top-down, always taking the rightest branch,
- until +CURSOR-MARKER+."
- (assert (listp form))
- (destructuring-bind (operator . args) form
- ;; N.b. the user's cursor is at the rightmost, deepest
- ;; subform right before +CURSOR-MARKER+.
- (let ((last-subform (car (last form)))
- (new-ops))
- (cond
- ((eq last-subform +cursor-marker+)
- (if (operator-p operator local-ops)
- (yield-success form local-ops)
- (yield-failure)))
- ((not (operator-p operator local-ops))
- (grovel-form last-subform local-ops))
- ;; Make sure to pick up the arglists of local
- ;; function/macro definitions.
- ((setq new-ops (extract-local-op-arglists operator args))
- (multiple-value-or (grovel-form last-subform
- (nconc new-ops local-ops))
- (yield-success form local-ops)))
- ;; Some typespecs clash with function names, so we make
- ;; sure to bail out early.
- ((member operator '(cl:declare cl:declaim))
- (yield-success form local-ops))
- ;; Mostly uninteresting, hence skip.
- ((memq operator '(cl:quote cl:function))
- (yield-failure))
- (t
- (multiple-value-or (grovel-form last-subform local-ops)
- (yield-success form local-ops))))))))
- (if (null form)
- (yield-failure)
- (grovel-form form '()))))
-
- (defun extract-cursor-marker (form)
- "Returns three values: normalized `form' without +CURSOR-MARKER+,
- the object in front of +CURSOR-MARKER+, and a form path to that
- object."
- (labels ((grovel (form last path)
- (let ((result-form))
- (loop for (car . cdr) on form do
- (cond ((eql car +cursor-marker+)
- (decf (first path))
- (return-from grovel
- (values (nreconc result-form cdr)
- last
- (nreverse path))))
- ((consp car)
- (multiple-value-bind (new-car new-last new-path)
- (grovel car last (cons 0 path))
- (when new-path ; CAR contained cursor-marker?
- (return-from grovel
- (values (nreconc
- (cons new-car result-form) cdr)
- new-last
- new-path))))))
- (push car result-form)
- (setq last car)
- (incf (first path))
- finally
- (return-from grovel
- (values (nreverse result-form) nil nil))))))
- (grovel form nil (list 0))))
-
- (defgeneric extract-local-op-arglists (operator args)
- (:documentation
- "If the form `(OPERATOR ,@ARGS) is a local operator binding form,
- return a list of pairs (OP . ARGLIST) for each locally bound op.")
- (:method (operator args)
- (declare (ignore operator args))
- nil)
- ;; FLET
- (:method ((operator (eql 'cl:flet)) args)
- (let ((defs (first args))
- (body (rest args)))
- (cond ((null body) nil) ; `(flet ((foo (x) |'
- ((atom defs) nil) ; `(flet ,foo (|'
- (t (%collect-op/argl-alist defs)))))
- ;; LABELS
- (:method ((operator (eql 'cl:labels)) args)
- ;; Notice that we only have information to "look backward" and
- ;; show arglists of previously occuring local functions.
- (destructuring-bind (defs . body) args
- (unless (or (atom defs) (null body)) ; `(labels ,foo (|'
- (let ((current-def (car (last defs))))
- (cond ((atom current-def) nil) ; `(labels ((foo (x) ...)|'
- ((not (null body))
- (extract-local-op-arglists 'cl:flet args))
- (t
- (let ((def.body (cddr current-def)))
- (when def.body
- (%collect-op/argl-alist defs)))))))))
- ;; MACROLET
- (:method ((operator (eql 'cl:macrolet)) args)
- (extract-local-op-arglists 'cl:labels args)))
-
- (defun %collect-op/argl-alist (defs)
- (setq defs (remove-if-not #'(lambda (x)
- ;; Well-formed FLET/LABELS def?
- (and (consp x) (second x)))
- defs))
- (loop for (name arglist . nil) in defs
- collect (cons name arglist)))
-
- (defun find-immediately-containing-arglist (form)
- "Returns the arglist of the subform _immediately_ containing
- +CURSOR-MARKER+ in `form'. Notice, however, that +CURSOR-MARKER+ may
- be in a nested arglist \(e.g. `(WITH-OPEN-FILE (<here>'\), and the
- arglist of the appropriate parent form \(WITH-OPEN-FILE\) will be
- returned in that case."
- (flet ((try (form-path form arglist)
- (let* ((arglist-path (form-path-to-arglist-path form-path
- form
- arglist))
- (argl (apply #'arglist-ref
- arglist
- arglist-path))
- (args (apply #'provided-arguments-ref
- (cdr form)
- arglist
- arglist-path)))
- (when (and (arglist-p argl) (listp args))
- (values argl args)))))
- (multiple-value-bind (form arglist obj form-path)
- (find-subform-with-arglist form)
- (declare (ignore obj))
- (with-available-arglist (arglist) arglist
- ;; First try the form the cursor is in (in case of a normal
- ;; form), then try the surrounding form (in case of a nested
- ;; macro form).
- (multiple-value-or (try form-path form arglist)
- (try (butlast form-path) form arglist)
- :not-available)))))
-
- (defun form-path-to-arglist-path (form-path form arglist)
- "Convert a form path to an arglist path consisting of arglist
- indices."
- (labels ((convert (path args arglist)
- (if (null path)
- nil
- (let* ((idx (car path))
- (idx* (arglist-index idx args arglist))
- (arglist* (and idx* (arglist-ref arglist idx*)))
- (args* (and idx* (provided-arguments-ref args
- arglist
- idx*))))
- ;; The FORM-PATH may be more detailed than ARGLIST;
- ;; consider (defun foo (x y) ...), a form path may
- ;; point into the function's lambda-list, but the
- ;; arglist of DEFUN won't contain as much information.
- ;; So we only recurse if possible.
- (cond ((null idx*)
- nil)
- ((arglist-p arglist*)
- (cons idx* (convert (cdr path) args* arglist*)))
- (t
- (list idx*)))))))
- (convert
- ;; FORM contains irrelevant operator. Adjust FORM-PATH.
- (cond ((null form-path) nil)
- ((equal form-path '(0)) nil)
- (t
- (destructuring-bind (car . cdr) form-path
- (cons (1- car) cdr))))
- (cdr form)
- arglist)))
-
- (defun arglist-index (provided-argument-index provided-arguments arglist)
- "Return the arglist index into `arglist' for the parameter belonging
- to the argument (NTH `provided-argument-index' `provided-arguments')."
- (let ((positional-args# (positional-args-number arglist))
- (arg-index provided-argument-index))
- (with-struct (arglist. key-p rest) arglist
- (cond
- ((< arg-index positional-args#) ; required + optional
- arg-index)
- ((and (not key-p) (not rest)) ; more provided than allowed
- nil)
- ((not key-p) ; rest + body
- (assert (arglist.rest arglist))
- positional-args#)
- (t ; key
- ;; Find last provided &key parameter
- (let* ((argument (nth arg-index provided-arguments))
- (provided-keys (subseq provided-arguments positional-args#)))
- (loop for (key value) on provided-keys by #'cddr
- when (eq value argument)
- return (match key
- (('quote symbol) symbol)
- (_ key)))))))))
-
- (defun arglist-ref (arglist &rest indices)
- "Returns the parameter in ARGLIST along the INDICIES path. Numbers
- represent positional parameters (required, optional), keywords
- represent key parameters."
- (flet ((ref-positional-arg (arglist index)
- (check-type index (integer 0 *))
- (with-struct (arglist. provided-args required-args
- optional-args rest)
- arglist
- (loop for args in (list provided-args required-args
- (mapcar #'optional-arg.arg-name
- optional-args))
- for args# = (length args)
- if (< index args#)
- return (nth index args)
- else
- do (decf index args#)
- finally (return (or rest nil)))))
- (ref-keyword-arg (arglist keyword)
- ;; keyword argument may be any symbol,
- ;; not only from the KEYWORD package.
- (let ((keyword (match keyword
- (('quote symbol) symbol)
- (_ keyword))))
- (do-decoded-arglist arglist
- (&key (kw arg) (when (eq kw keyword)
- (return-from ref-keyword-arg arg)))))
- nil))
- (dolist (index indices)
- (assert (arglist-p arglist))
- (setq arglist (if (numberp index)
- (ref-positional-arg arglist index)
- (ref-keyword-arg arglist index))))
- arglist))
-
- (defun provided-arguments-ref (provided-args arglist &rest indices)
- "Returns the argument in PROVIDED-ARGUMENT along the INDICES path
- relative to ARGLIST."
- (check-type arglist arglist)
- (flet ((ref (provided-args arglist index)
- (if (numberp index)
- (nth index provided-args)
- (let ((provided-keys (subseq provided-args
- (positional-args-number arglist))))
- (loop for (key value) on provided-keys
- when (eq key index)
- return value)))))
- (dolist (idx indices)
- (setq provided-args (ref provided-args arglist idx))
- (setq arglist (arglist-ref arglist idx)))
- provided-args))
-
- (defun positional-args-number (arglist)
- (+ (length (arglist.provided-args arglist))
- (length (arglist.required-args arglist))
- (length (arglist.optional-args arglist))))
-
- (defun parse-raw-form (raw-form)
- "Parse a RAW-FORM into a Lisp form. I.e. substitute strings by
- symbols if already interned. For strings not already interned, use
- ARGLIST-DUMMY."
- (unless (null raw-form)
- (loop for element in raw-form
- collect (etypecase element
- (string (read-conversatively element))
- (list (parse-raw-form element))
- (symbol (prog1 element
- ;; Comes after list, so ELEMENT can't be NIL.
- (assert (eq element +cursor-marker+))))))))
-
- (defun read-conversatively (string)
- "Tries to find the symbol that's represented by STRING.
-
- If it can't, this either means that STRING does not represent a
- symbol, or that the symbol behind STRING would have to be freshly
- interned. Because this function is supposed to be called from the
- automatic arglist display stuff from Slime, interning freshly
- symbols is a big no-no.
-
- In such a case (that no symbol could be found), an object of type
- ARGLIST-DUMMY is returned instead, which works as a placeholder
- datum for subsequent logics to rely on."
- (let* ((string (string-left-trim '(#\Space #\Tab #\Newline) string))
- (length (length string))
- (type (cond ((zerop length) nil)
- ((eql (aref string 0) #\')
- :quoted-symbol)
- ((search "#'" string :end2 (min length 2))
- :sharpquoted-symbol)
- ((char= (char string 0) (char string (1- length))
- #\")
- :string)
- (t
- :symbol))))
- (multiple-value-bind (symbol found?)
- (case type
- (:symbol (parse-symbol string))
- (:quoted-symbol (parse-symbol (subseq string 1)))
- (:sharpquoted-symbol (parse-symbol (subseq string 2)))
- (:string (values string t))
- (t (values string nil)))
- (if found?
- (ecase type
- (:symbol symbol)
- (:quoted-symbol `(quote ,symbol))
- (:sharpquoted-symbol `(function ,symbol))
- (:string (if (> length 1)
- (subseq string 1 (1- length))
- string)))
- (make-arglist-dummy string)))))
-
- (defun test-print-arglist ()
- (flet ((test (arglist &rest strings)
- (let* ((*package* (find-package :swank))
- (actual (decoded-arglist-to-string
- (decode-arglist arglist)
- :print-right-margin 1000)))
- (unless (loop for string in strings
- thereis (string= actual string))
- (warn "Test failed: ~S => ~S~% Expected: ~A"
- arglist actual
- (if (cdr strings)
- (format nil "One of: ~{~S~^, ~}" strings)
- (format nil "~S" (first strings))))))))
- (test '(function cons) "(function cons)")
- (test '(quote cons) "(quote cons)")
- (test '(&key (function #'+))
- "(&key (function #'+))" "(&key (function (function +)))")
- (test '(&whole x y z) "(y z)")
- (test '(x &aux y z) "(x)")
- (test '(x &environment env y) "(x y)")
- (test '(&key ((function f))) "(&key ((function ..)))")
- (test
- '(eval-when (&any :compile-toplevel :load-toplevel :execute) &body body)
- "(eval-when (&any :compile-toplevel :load-toplevel :execute) &body body)")
- (test '(declare (optimize &any (speed 1) (safety 1)))
- "(declare (optimize &any (speed 1) (safety 1)))")))
-
- (defun test-arglist-ref ()
- (macrolet ((soft-assert (form)
- `(unless ,form
- (warn "Assertion failed: ~S~%" ',form))))
- (let ((sample (decode-arglist '(x &key ((:k (y z)))))))
- (soft-assert (eq (arglist-ref sample 0) 'x))
- (soft-assert (eq (arglist-ref sample :k 0) 'y))
- (soft-assert (eq (arglist-ref sample :k 1) 'z))
-
- (soft-assert (eq (provided-arguments-ref '(a :k (b c)) sample 0)
- 'a))
- (soft-assert (eq (provided-arguments-ref '(a :k (b c)) sample :k 0)
- 'b))
- (soft-assert (eq (provided-arguments-ref '(a :k (b c)) sample :k 1)
- 'c)))))
-
- (test-print-arglist)
- (test-arglist-ref)
-
- (provide :swank-arglists)
|