Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

358 regels
14 KiB

4 jaren geleden
  1. (require 'slime)
  2. (require 'cl-lib)
  3. (define-slime-contrib slime-parse
  4. "Utility contrib containg functions to parse forms in a buffer."
  5. (:authors "Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>"
  6. "Tobias C. Rittweiler <tcr@freebits.de>")
  7. (:license "GPL"))
  8. (defun slime-parse-form-until (limit form-suffix)
  9. "Parses form from point to `limit'."
  10. ;; For performance reasons, this function does not use recursion.
  11. (let ((todo (list (point))) ; stack of positions
  12. (sexps) ; stack of expressions
  13. (cursexp)
  14. (curpos)
  15. (depth 1)) ; This function must be called from the
  16. ; start of the sexp to be parsed.
  17. (while (and (setq curpos (pop todo))
  18. (progn
  19. (goto-char curpos)
  20. ;; (Here we also move over suppressed
  21. ;; reader-conditionalized code! Important so CL-side
  22. ;; of autodoc won't see that garbage.)
  23. (ignore-errors (slime-forward-cruft))
  24. (< (point) limit)))
  25. (setq cursexp (pop sexps))
  26. (cond
  27. ;; End of an sexp?
  28. ((or (looking-at "\\s)") (eolp))
  29. (cl-decf depth)
  30. (push (nreverse cursexp) (car sexps)))
  31. ;; Start of a new sexp?
  32. ((looking-at "\\s'*@*\\s(")
  33. (let ((subpt (match-end 0)))
  34. (ignore-errors
  35. (forward-sexp)
  36. ;; (In case of error, we're at an incomplete sexp, and
  37. ;; nothing's left todo after it.)
  38. (push (point) todo))
  39. (push cursexp sexps)
  40. (push subpt todo) ; to descend into new sexp
  41. (push nil sexps)
  42. (cl-incf depth)))
  43. ;; In mid of an sexp..
  44. (t
  45. (let ((pt1 (point))
  46. (pt2 (condition-case e
  47. (progn (forward-sexp) (point))
  48. (scan-error
  49. (cl-fourth e))))) ; end of sexp
  50. (push (buffer-substring-no-properties pt1 pt2) cursexp)
  51. (push pt2 todo)
  52. (push cursexp sexps)))))
  53. (when sexps
  54. (setf (car sexps) (cl-nreconc form-suffix (car sexps)))
  55. (while (> depth 1)
  56. (push (nreverse (pop sexps)) (car sexps))
  57. (cl-decf depth))
  58. (nreverse (car sexps)))))
  59. (defun slime-compare-char-syntax (get-char-fn syntax &optional unescaped)
  60. "Returns t if the character that `get-char-fn' yields has
  61. characer syntax of `syntax'. If `unescaped' is true, it's ensured
  62. that the character is not escaped."
  63. (let ((char (funcall get-char-fn (point)))
  64. (char-before (funcall get-char-fn (1- (point)))))
  65. (if (and char (eq (char-syntax char) (aref syntax 0)))
  66. (if unescaped
  67. (or (null char-before)
  68. (not (eq (char-syntax char-before) ?\\)))
  69. t)
  70. nil)))
  71. (defconst slime-cursor-marker 'swank::%cursor-marker%)
  72. (defun slime-parse-form-upto-point (&optional max-levels)
  73. (save-restriction
  74. ;; Don't parse more than 500 lines before point, so we don't spend
  75. ;; too much time. NB. Make sure to go to beginning of line, and
  76. ;; not possibly anywhere inside comments or strings.
  77. (narrow-to-region (line-beginning-position -500) (point-max))
  78. (save-excursion
  79. (let ((suffix (list slime-cursor-marker)))
  80. (cond ((slime-compare-char-syntax #'char-after "(" t)
  81. ;; We're at the start of some expression, so make sure
  82. ;; that SWANK::%CURSOR-MARKER% will come after that
  83. ;; expression. If the expression is not balanced, make
  84. ;; still sure that the marker does *not* come directly
  85. ;; after the preceding expression.
  86. (or (ignore-errors (forward-sexp) t)
  87. (push "" suffix)))
  88. ((or (bolp) (slime-compare-char-syntax #'char-before " " t))
  89. ;; We're after some expression, so we have to make sure
  90. ;; that %CURSOR-MARKER% does *not* come directly after
  91. ;; that expression.
  92. (push "" suffix))
  93. ((slime-compare-char-syntax #'char-before "(" t)
  94. ;; We're directly after an opening parenthesis, so we
  95. ;; have to make sure that something comes before
  96. ;; %CURSOR-MARKER%.
  97. (push "" suffix))
  98. (t
  99. ;; We're at a symbol, so make sure we get the whole symbol.
  100. (slime-end-of-symbol)))
  101. (let ((pt (point)))
  102. (ignore-errors (up-list (if max-levels (- max-levels) -5)))
  103. (ignore-errors (down-list))
  104. (slime-parse-form-until pt suffix))))))
  105. (require 'bytecomp)
  106. (mapc (lambda (sym)
  107. (cond ((fboundp sym)
  108. (unless (byte-code-function-p (symbol-function sym))
  109. (byte-compile sym)))
  110. (t (error "%S is not fbound" sym))))
  111. '(slime-parse-form-upto-point
  112. slime-parse-form-until
  113. slime-compare-char-syntax))
  114. ;;;; Test cases
  115. (defun slime-extract-context ()
  116. "Parse the context for the symbol at point.
  117. Nil is returned if there's no symbol at point. Otherwise we detect
  118. the following cases (the . shows the point position):
  119. (defun n.ame (...) ...) -> (:defun name)
  120. (defun (setf n.ame) (...) ...) -> (:defun (setf name))
  121. (defmethod n.ame (...) ...) -> (:defmethod name (...))
  122. (defun ... (...) (labels ((n.ame (...) -> (:labels (:defun ...) name)
  123. (defun ... (...) (flet ((n.ame (...) -> (:flet (:defun ...) name)
  124. (defun ... (...) ... (n.ame ...) ...) -> (:call (:defun ...) name)
  125. (defun ... (...) ... (setf (n.ame ...) -> (:call (:defun ...) (setf name))
  126. (defmacro n.ame (...) ...) -> (:defmacro name)
  127. (defsetf n.ame (...) ...) -> (:defsetf name)
  128. (define-setf-expander n.ame (...) ...) -> (:define-setf-expander name)
  129. (define-modify-macro n.ame (...) ...) -> (:define-modify-macro name)
  130. (define-compiler-macro n.ame (...) ...) -> (:define-compiler-macro name)
  131. (defvar n.ame (...) ...) -> (:defvar name)
  132. (defparameter n.ame ...) -> (:defparameter name)
  133. (defconstant n.ame ...) -> (:defconstant name)
  134. (defclass n.ame ...) -> (:defclass name)
  135. (defstruct n.ame ...) -> (:defstruct name)
  136. (defpackage n.ame ...) -> (:defpackage name)
  137. For other contexts we return the symbol at point."
  138. (let ((name (slime-symbol-at-point)))
  139. (if name
  140. (let ((symbol (read name)))
  141. (or (progn ;;ignore-errors
  142. (slime-parse-context symbol))
  143. symbol)))))
  144. (defun slime-parse-context (name)
  145. (save-excursion
  146. (cond ((slime-in-expression-p '(defun *)) `(:defun ,name))
  147. ((slime-in-expression-p '(defmacro *)) `(:defmacro ,name))
  148. ((slime-in-expression-p '(defgeneric *)) `(:defgeneric ,name))
  149. ((slime-in-expression-p '(setf *))
  150. ;;a setf-definition, but which?
  151. (backward-up-list 1)
  152. (slime-parse-context `(setf ,name)))
  153. ((slime-in-expression-p '(defmethod *))
  154. (unless (looking-at "\\s ")
  155. (forward-sexp 1)) ; skip over the methodname
  156. (let (qualifiers arglist)
  157. (cl-loop for e = (read (current-buffer))
  158. until (listp e) do (push e qualifiers)
  159. finally (setq arglist e))
  160. `(:defmethod ,name ,@qualifiers
  161. ,(slime-arglist-specializers arglist))))
  162. ((and (symbolp name)
  163. (slime-in-expression-p `(,name)))
  164. ;; looks like a regular call
  165. (let ((toplevel (ignore-errors (slime-parse-toplevel-form))))
  166. (cond ((slime-in-expression-p `(setf (*))) ;a setf-call
  167. (if toplevel
  168. `(:call ,toplevel (setf ,name))
  169. `(setf ,name)))
  170. ((not toplevel)
  171. name)
  172. ((slime-in-expression-p `(labels ((*))))
  173. `(:labels ,toplevel ,name))
  174. ((slime-in-expression-p `(flet ((*))))
  175. `(:flet ,toplevel ,name))
  176. (t
  177. `(:call ,toplevel ,name)))))
  178. ((slime-in-expression-p '(define-compiler-macro *))
  179. `(:define-compiler-macro ,name))
  180. ((slime-in-expression-p '(define-modify-macro *))
  181. `(:define-modify-macro ,name))
  182. ((slime-in-expression-p '(define-setf-expander *))
  183. `(:define-setf-expander ,name))
  184. ((slime-in-expression-p '(defsetf *))
  185. `(:defsetf ,name))
  186. ((slime-in-expression-p '(defvar *)) `(:defvar ,name))
  187. ((slime-in-expression-p '(defparameter *)) `(:defparameter ,name))
  188. ((slime-in-expression-p '(defconstant *)) `(:defconstant ,name))
  189. ((slime-in-expression-p '(defclass *)) `(:defclass ,name))
  190. ((slime-in-expression-p '(defpackage *)) `(:defpackage ,name))
  191. ((slime-in-expression-p '(defstruct *))
  192. `(:defstruct ,(if (consp name)
  193. (car name)
  194. name)))
  195. (t
  196. name))))
  197. (defun slime-in-expression-p (pattern)
  198. "A helper function to determine the current context.
  199. The pattern can have the form:
  200. pattern ::= () ;matches always
  201. | (*) ;matches inside a list
  202. | (<symbol> <pattern>) ;matches if the first element in
  203. ; the current list is <symbol> and
  204. ; if <pattern> matches.
  205. | ((<pattern>)) ;matches if we are in a nested list."
  206. (save-excursion
  207. (let ((path (reverse (slime-pattern-path pattern))))
  208. (cl-loop for p in path
  209. always (ignore-errors
  210. (cl-etypecase p
  211. (symbol (slime-beginning-of-list)
  212. (eq (read (current-buffer)) p))
  213. (number (backward-up-list p)
  214. t)))))))
  215. (defun slime-pattern-path (pattern)
  216. ;; Compute the path to the * in the pattern to make matching
  217. ;; easier. The path is a list of symbols and numbers. A number
  218. ;; means "(down-list <n>)" and a symbol "(look-at <sym>)")
  219. (if (null pattern)
  220. '()
  221. (cl-etypecase (car pattern)
  222. ((member *) '())
  223. (symbol (cons (car pattern) (slime-pattern-path (cdr pattern))))
  224. (cons (cons 1 (slime-pattern-path (car pattern)))))))
  225. (defun slime-beginning-of-list (&optional up)
  226. "Move backward to the beginning of the current expression.
  227. Point is placed before the first expression in the list."
  228. (backward-up-list (or up 1))
  229. (down-list 1)
  230. (skip-syntax-forward " "))
  231. (defun slime-end-of-list (&optional up)
  232. (backward-up-list (or up 1))
  233. (forward-list 1)
  234. (down-list -1))
  235. (defun slime-parse-toplevel-form ()
  236. (ignore-errors ; (foo)
  237. (save-excursion
  238. (goto-char (car (slime-region-for-defun-at-point)))
  239. (down-list 1)
  240. (forward-sexp 1)
  241. (slime-parse-context (read (current-buffer))))))
  242. (defun slime-arglist-specializers (arglist)
  243. (cond ((or (null arglist)
  244. (member (cl-first arglist) '(&optional &key &rest &aux)))
  245. (list))
  246. ((consp (cl-first arglist))
  247. (cons (cl-second (cl-first arglist))
  248. (slime-arglist-specializers (cl-rest arglist))))
  249. (t
  250. (cons 't
  251. (slime-arglist-specializers (cl-rest arglist))))))
  252. (defun slime-definition-at-point (&optional only-functional)
  253. "Return object corresponding to the definition at point."
  254. (let ((toplevel (slime-parse-toplevel-form)))
  255. (if (or (symbolp toplevel)
  256. (and only-functional
  257. (not (member (car toplevel)
  258. '(:defun :defgeneric :defmethod
  259. :defmacro :define-compiler-macro)))))
  260. (error "Not in a definition")
  261. (slime-dcase toplevel
  262. (((:defun :defgeneric) symbol)
  263. (format "#'%s" symbol))
  264. (((:defmacro :define-modify-macro) symbol)
  265. (format "(macro-function '%s)" symbol))
  266. ((:define-compiler-macro symbol)
  267. (format "(compiler-macro-function '%s)" symbol))
  268. ((:defmethod symbol &rest args)
  269. (declare (ignore args))
  270. (format "#'%s" symbol))
  271. (((:defparameter :defvar :defconstant) symbol)
  272. (format "'%s" symbol))
  273. (((:defclass :defstruct) symbol)
  274. (format "(find-class '%s)" symbol))
  275. ((:defpackage symbol)
  276. (format "(or (find-package '%s) (error \"Package %s not found\"))"
  277. symbol symbol))
  278. (t
  279. (error "Not in a definition"))))))
  280. (defsubst slime-current-parser-state ()
  281. ;; `syntax-ppss' does not save match data as it invokes
  282. ;; `beginning-of-defun' implicitly which does not save match
  283. ;; data. This issue has been reported to the Emacs maintainer on
  284. ;; Feb27.
  285. (syntax-ppss))
  286. (defun slime-inside-string-p ()
  287. (nth 3 (slime-current-parser-state)))
  288. (defun slime-inside-comment-p ()
  289. (nth 4 (slime-current-parser-state)))
  290. (defun slime-inside-string-or-comment-p ()
  291. (let ((state (slime-current-parser-state)))
  292. (or (nth 3 state) (nth 4 state))))
  293. ;;; The following two functions can be handy when inspecting
  294. ;;; source-location while debugging `M-.'.
  295. ;;;
  296. (defun slime-current-tlf-number ()
  297. "Return the current toplevel number."
  298. (interactive)
  299. (let ((original-pos (car (slime-region-for-defun-at-point)))
  300. (n 0))
  301. (save-excursion
  302. ;; We use this and no repeated `beginning-of-defun's to get
  303. ;; reader conditionals right.
  304. (goto-char (point-min))
  305. (while (progn (slime-forward-sexp)
  306. (< (point) original-pos))
  307. (cl-incf n)))
  308. n))
  309. ;;; This is similiar to `slime-enclosing-form-paths' in the
  310. ;;; `slime-parse' contrib except that this does not do any duck-tape
  311. ;;; parsing, and gets reader conditionals right.
  312. (defun slime-current-form-path ()
  313. "Returns the path from the beginning of the current toplevel
  314. form to the atom at point, or nil if we're in front of a tlf."
  315. (interactive)
  316. (let ((source-path nil))
  317. (save-excursion
  318. ;; Moving forward to get reader conditionals right.
  319. (cl-loop for inner-pos = (point)
  320. for outer-pos = (cl-nth-value 1 (slime-current-parser-state))
  321. while outer-pos do
  322. (goto-char outer-pos)
  323. (unless (eq (char-before) ?#) ; when at #(...) continue.
  324. (forward-char)
  325. (let ((n 0))
  326. (while (progn (slime-forward-sexp)
  327. (< (point) inner-pos))
  328. (cl-incf n))
  329. (push n source-path)
  330. (goto-char outer-pos)))))
  331. source-path))
  332. (provide 'slime-parse)