Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/slime/contrib/slime-parse.el
990 views
1
2
(define-slime-contrib slime-parse
3
"Utility contrib containg functions to parse forms in a buffer."
4
(:authors "Matthias Koeppe <[email protected]>"
5
"Tobias C. Rittweiler <[email protected]>")
6
(:license "GPL"))
7
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
(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
(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
(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) (nreconc form-suffix (car sexps)))
55
(while (> depth 1)
56
(push (nreverse (pop sexps)) (car sexps))
57
(decf depth))
58
(nreverse (car sexps)))))
59
60
(defun slime-compare-char-syntax (get-char-fn syntax &optional unescaped)
61
"Returns t if the character that `get-char-fn' yields has
62
characer syntax of `syntax'. If `unescaped' is true, it's ensured
63
that the character is not escaped."
64
(let ((char (funcall get-char-fn (point)))
65
(char-before (funcall get-char-fn (1- (point)))))
66
(if (and char (eq (char-syntax char) (aref syntax 0)))
67
(if unescaped
68
(or (null char-before)
69
(not (eq (char-syntax char-before) ?\\)))
70
t)
71
nil)))
72
73
(defconst slime-cursor-marker 'swank::%cursor-marker%)
74
75
(defun slime-parse-form-upto-point (&optional max-levels)
76
(save-restriction
77
;; Don't parse more than 500 lines before point, so we don't spend
78
;; too much time. NB. Make sure to go to beginning of line, and
79
;; not possibly anywhere inside comments or strings.
80
(narrow-to-region (line-beginning-position -500) (point-max))
81
(save-excursion
82
(let ((suffix (list slime-cursor-marker)))
83
(cond ((slime-compare-char-syntax #'char-after "(" t)
84
;; We're at the start of some expression, so make sure
85
;; that SWANK::%CURSOR-MARKER% will come after that
86
;; expression.
87
(ignore-errors (forward-sexp)))
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 that
91
;; 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
106
(let ((byte-compile-warnings '()))
107
(mapc #'byte-compile
108
'(slime-parse-form-upto-point
109
slime-parse-form-until
110
slime-compare-char-syntax
111
)))
112
113
;;;; Test cases
114
115
(defun slime-check-buffer-form (result-form)
116
(slime-test-expect
117
(format "Buffer form correct in `%s' (at %d)" (buffer-string) (point))
118
result-form
119
(slime-parse-form-upto-point 10)))
120
121
(def-slime-test form-up-to-point.1
122
(buffer-sexpr result-form &optional skip-trailing-test-p)
123
""
124
'(("(char= #\\(*HERE*" ("char=" "#\\(" swank::%cursor-marker%))
125
("(char= #\\( *HERE*" ("char=" "#\\(" "" swank::%cursor-marker%))
126
("(char= #\\) *HERE*" ("char=" "#\\)" "" swank::%cursor-marker%))
127
("(char= #\\*HERE*" ("char=" "#\\" swank::%cursor-marker%) t)
128
("(defun*HERE*" ("defun" swank::%cursor-marker%))
129
("(defun foo*HERE*" ("defun" "foo" swank::%cursor-marker%))
130
("(defun foo (x y)*HERE*" ("defun" "foo" ("x" "y") swank::%cursor-marker%))
131
("(defun foo (x y*HERE*" ("defun" "foo" ("x" "y" swank::%cursor-marker%)))
132
("(apply 'foo*HERE*" ("apply" "'foo" swank::%cursor-marker%))
133
("(apply #'foo*HERE*" ("apply" "#'foo" swank::%cursor-marker%))
134
("(declare ((vector bit *HERE*" ("declare" (("vector" "bit" "" swank::%cursor-marker%))))
135
("(with-open-file (*HERE*" ("with-open-file" ("" swank::%cursor-marker%)))
136
("(((*HERE*" ((("" swank::%cursor-marker%))))
137
("(defun #| foo #| *HERE*" ("defun" "" swank::%cursor-marker%))
138
("(defun #-(and) (bar) f*HERE*" ("defun" "f" swank::%cursor-marker%))
139
("(remove-if #'(lambda (x)*HERE*" ("remove-if" ("lambda" ("x") swank::%cursor-marker%)))
140
("`(remove-if ,(lambda (x)*HERE*" ("remove-if" ("lambda" ("x") swank::%cursor-marker%)))
141
("`(remove-if ,@(lambda (x)*HERE*" ("remove-if" ("lambda" ("x") swank::%cursor-marker%))))
142
(slime-check-top-level)
143
(with-temp-buffer
144
(lisp-mode)
145
(insert buffer-sexpr)
146
(search-backward "*HERE*")
147
(delete-region (match-beginning 0) (match-end 0))
148
(slime-check-buffer-form result-form)
149
(unless skip-trailing-test-p
150
(insert ")") (backward-char)
151
(slime-check-buffer-form result-form))
152
))
153
154
(defun slime-trace-query (spec)
155
"Ask the user which function to trace; SPEC is the default.
156
The result is a string."
157
(cond ((null spec)
158
(slime-read-from-minibuffer "(Un)trace: "))
159
((stringp spec)
160
(slime-read-from-minibuffer "(Un)trace: " spec))
161
((symbolp spec) ; `slime-extract-context' can return symbols.
162
(slime-read-from-minibuffer "(Un)trace: " (prin1-to-string spec)))
163
(t
164
(destructure-case spec
165
((setf n)
166
(slime-read-from-minibuffer "(Un)trace: " (prin1-to-string spec)))
167
((:defun n)
168
(slime-read-from-minibuffer "(Un)trace: " (prin1-to-string n)))
169
((:defgeneric n)
170
(let* ((name (prin1-to-string n))
171
(answer (slime-read-from-minibuffer "(Un)trace: " name)))
172
(cond ((and (string= name answer)
173
(y-or-n-p (concat "(Un)trace also all "
174
"methods implementing "
175
name "? ")))
176
(prin1-to-string `(:defgeneric ,n)))
177
(t
178
answer))))
179
((:defmethod &rest _)
180
(slime-read-from-minibuffer "(Un)trace: " (prin1-to-string spec)))
181
((:call caller callee)
182
(let* ((callerstr (prin1-to-string caller))
183
(calleestr (prin1-to-string callee))
184
(answer (slime-read-from-minibuffer "(Un)trace: "
185
calleestr)))
186
(cond ((and (string= calleestr answer)
187
(y-or-n-p (concat "(Un)trace only when " calleestr
188
" is called by " callerstr "? ")))
189
(prin1-to-string `(:call ,caller ,callee)))
190
(t
191
answer))))
192
(((:labels :flet) &rest _)
193
(slime-read-from-minibuffer "(Un)trace local function: "
194
(prin1-to-string spec)))
195
(t (error "Don't know how to trace the spec %S" spec))))))
196
197
(defun slime-extract-context ()
198
"Parse the context for the symbol at point.
199
Nil is returned if there's no symbol at point. Otherwise we detect
200
the following cases (the . shows the point position):
201
202
(defun n.ame (...) ...) -> (:defun name)
203
(defun (setf n.ame) (...) ...) -> (:defun (setf name))
204
(defmethod n.ame (...) ...) -> (:defmethod name (...))
205
(defun ... (...) (labels ((n.ame (...) -> (:labels (:defun ...) name)
206
(defun ... (...) (flet ((n.ame (...) -> (:flet (:defun ...) name)
207
(defun ... (...) ... (n.ame ...) ...) -> (:call (:defun ...) name)
208
(defun ... (...) ... (setf (n.ame ...) -> (:call (:defun ...) (setf name))
209
210
(defmacro n.ame (...) ...) -> (:defmacro name)
211
(defsetf n.ame (...) ...) -> (:defsetf name)
212
(define-setf-expander n.ame (...) ...) -> (:define-setf-expander name)
213
(define-modify-macro n.ame (...) ...) -> (:define-modify-macro name)
214
(define-compiler-macro n.ame (...) ...) -> (:define-compiler-macro name)
215
(defvar n.ame (...) ...) -> (:defvar name)
216
(defparameter n.ame ...) -> (:defparameter name)
217
(defconstant n.ame ...) -> (:defconstant name)
218
(defclass n.ame ...) -> (:defclass name)
219
(defstruct n.ame ...) -> (:defstruct name)
220
(defpackage n.ame ...) -> (:defpackage name)
221
For other contexts we return the symbol at point."
222
(let ((name (slime-symbol-at-point)))
223
(if name
224
(let ((symbol (read name)))
225
(or (progn ;;ignore-errors
226
(slime-parse-context symbol))
227
symbol)))))
228
229
(defun slime-parse-context (name)
230
(save-excursion
231
(cond ((slime-in-expression-p '(defun *)) `(:defun ,name))
232
((slime-in-expression-p '(defmacro *)) `(:defmacro ,name))
233
((slime-in-expression-p '(defgeneric *)) `(:defgeneric ,name))
234
((slime-in-expression-p '(setf *))
235
;;a setf-definition, but which?
236
(backward-up-list 1)
237
(slime-parse-context `(setf ,name)))
238
((slime-in-expression-p '(defmethod *))
239
(unless (looking-at "\\s ")
240
(forward-sexp 1)) ; skip over the methodname
241
(let (qualifiers arglist)
242
(loop for e = (read (current-buffer))
243
until (listp e) do (push e qualifiers)
244
finally (setq arglist e))
245
`(:defmethod ,name ,@qualifiers
246
,(slime-arglist-specializers arglist))))
247
((and (symbolp name)
248
(slime-in-expression-p `(,name)))
249
;; looks like a regular call
250
(let ((toplevel (ignore-errors (slime-parse-toplevel-form))))
251
(cond ((slime-in-expression-p `(setf (*))) ;a setf-call
252
(if toplevel
253
`(:call ,toplevel (setf ,name))
254
`(setf ,name)))
255
((not toplevel)
256
name)
257
((slime-in-expression-p `(labels ((*))))
258
`(:labels ,toplevel ,name))
259
((slime-in-expression-p `(flet ((*))))
260
`(:flet ,toplevel ,name))
261
(t
262
`(:call ,toplevel ,name)))))
263
((slime-in-expression-p '(define-compiler-macro *))
264
`(:define-compiler-macro ,name))
265
((slime-in-expression-p '(define-modify-macro *))
266
`(:define-modify-macro ,name))
267
((slime-in-expression-p '(define-setf-expander *))
268
`(:define-setf-expander ,name))
269
((slime-in-expression-p '(defsetf *))
270
`(:defsetf ,name))
271
((slime-in-expression-p '(defvar *)) `(:defvar ,name))
272
((slime-in-expression-p '(defparameter *)) `(:defparameter ,name))
273
((slime-in-expression-p '(defconstant *)) `(:defconstant ,name))
274
((slime-in-expression-p '(defclass *)) `(:defclass ,name))
275
((slime-in-expression-p '(defpackage *)) `(:defpackage ,name))
276
((slime-in-expression-p '(defstruct *))
277
`(:defstruct ,(if (consp name)
278
(car name)
279
name)))
280
(t
281
name))))
282
283
284
(defun slime-in-expression-p (pattern)
285
"A helper function to determine the current context.
286
The pattern can have the form:
287
pattern ::= () ;matches always
288
| (*) ;matches inside a list
289
| (<symbol> <pattern>) ;matches if the first element in
290
; the current list is <symbol> and
291
; if <pattern> matches.
292
| ((<pattern>)) ;matches if we are in a nested list."
293
(save-excursion
294
(let ((path (reverse (slime-pattern-path pattern))))
295
(loop for p in path
296
always (ignore-errors
297
(etypecase p
298
(symbol (slime-beginning-of-list)
299
(eq (read (current-buffer)) p))
300
(number (backward-up-list p)
301
t)))))))
302
303
(defun slime-pattern-path (pattern)
304
;; Compute the path to the * in the pattern to make matching
305
;; easier. The path is a list of symbols and numbers. A number
306
;; means "(down-list <n>)" and a symbol "(look-at <sym>)")
307
(if (null pattern)
308
'()
309
(etypecase (car pattern)
310
((member *) '())
311
(symbol (cons (car pattern) (slime-pattern-path (cdr pattern))))
312
(cons (cons 1 (slime-pattern-path (car pattern)))))))
313
314
(defun slime-beginning-of-list (&optional up)
315
"Move backward to the beginning of the current expression.
316
Point is placed before the first expression in the list."
317
(backward-up-list (or up 1))
318
(down-list 1)
319
(skip-syntax-forward " "))
320
321
(defun slime-end-of-list (&optional up)
322
(backward-up-list (or up 1))
323
(forward-list 1)
324
(down-list -1))
325
326
(defun slime-parse-toplevel-form ()
327
(ignore-errors ; (foo)
328
(save-excursion
329
(goto-char (car (slime-region-for-defun-at-point)))
330
(down-list 1)
331
(forward-sexp 1)
332
(slime-parse-context (read (current-buffer))))))
333
334
(defun slime-arglist-specializers (arglist)
335
(cond ((or (null arglist)
336
(member (first arglist) '(&optional &key &rest &aux)))
337
(list))
338
((consp (first arglist))
339
(cons (second (first arglist))
340
(slime-arglist-specializers (rest arglist))))
341
(t
342
(cons 't
343
(slime-arglist-specializers (rest arglist))))))
344
345
(defun slime-definition-at-point (&optional only-functional)
346
"Return object corresponding to the definition at point."
347
(let ((toplevel (slime-parse-toplevel-form)))
348
(if (or (symbolp toplevel)
349
(and only-functional
350
(not (member (car toplevel)
351
'(:defun :defgeneric :defmethod
352
:defmacro :define-compiler-macro)))))
353
(error "Not in a definition")
354
(destructure-case toplevel
355
(((:defun :defgeneric) symbol)
356
(format "#'%s" symbol))
357
(((:defmacro :define-modify-macro) symbol)
358
(format "(macro-function '%s)" symbol))
359
((:define-compiler-macro symbol)
360
(format "(compiler-macro-function '%s)" symbol))
361
((:defmethod symbol &rest args)
362
(declare (ignore args))
363
(format "#'%s" symbol))
364
(((:defparameter :defvar :defconstant) symbol)
365
(format "'%s" symbol))
366
(((:defclass :defstruct) symbol)
367
(format "(find-class '%s)" symbol))
368
((:defpackage symbol)
369
(format "(or (find-package '%s) (error \"Package %s not found\"))"
370
symbol symbol))
371
(t
372
(error "Not in a definition"))))))
373
374
;; FIXME: not used here; move it away
375
(if (and (featurep 'emacs) (>= emacs-major-version 22))
376
;; N.B. The 2nd, and 6th return value cannot be relied upon.
377
(defsubst slime-current-parser-state ()
378
;; `syntax-ppss' does not save match data as it invokes
379
;; `beginning-of-defun' implicitly which does not save match
380
;; data. This issue has been reported to the Emacs maintainer on
381
;; Feb27.
382
(syntax-ppss))
383
(defsubst slime-current-parser-state ()
384
(let ((original-pos (point)))
385
(save-excursion
386
(beginning-of-defun)
387
(parse-partial-sexp (point) original-pos)))))
388
389
(defun slime-inside-string-p ()
390
(nth 3 (slime-current-parser-state)))
391
392
(defun slime-inside-comment-p ()
393
(nth 4 (slime-current-parser-state)))
394
395
(defun slime-inside-string-or-comment-p ()
396
(let ((state (slime-current-parser-state)))
397
(or (nth 3 state) (nth 4 state))))
398
399
(provide 'slime-parse)
400
401