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.

227 lines
7.9 KiB

5 years ago
  1. ;;; swank-macrostep.lisp -- fancy macro-expansion via macrostep.el
  2. ;;
  3. ;; Authors: Luis Oliveira <luismbo@gmail.com>
  4. ;; Jon Oddie <j.j.oddie@gmail.com>
  5. ;;
  6. ;; License: Public Domain
  7. (defpackage swank-macrostep
  8. (:use cl swank)
  9. (:import-from swank
  10. #:*macroexpand-printer-bindings*
  11. #:with-buffer-syntax
  12. #:with-bindings
  13. #:to-string
  14. #:macroexpand-all
  15. #:compiler-macroexpand-1
  16. #:defslimefun
  17. #:collect-macro-forms)
  18. (:export #:macrostep-expand-1
  19. #:macro-form-p))
  20. (in-package #:swank-macrostep)
  21. (defslimefun macrostep-expand-1 (string compiler-macros? context)
  22. (with-buffer-syntax ()
  23. (let ((form (read-from-string string)))
  24. (multiple-value-bind (expansion error-message)
  25. (expand-form-once form compiler-macros? context)
  26. (if error-message
  27. `(:error ,error-message)
  28. (multiple-value-bind (macros compiler-macros)
  29. (collect-macro-forms-in-context expansion context)
  30. (let* ((all-macros (append macros compiler-macros))
  31. (pretty-expansion (pprint-to-string expansion))
  32. (positions (collect-form-positions expansion
  33. pretty-expansion
  34. all-macros))
  35. (subform-info
  36. (loop
  37. for form in all-macros
  38. for (start end) in positions
  39. when (and start end)
  40. collect (let ((op-name (to-string (first form)))
  41. (op-type
  42. (if (member form macros)
  43. :macro
  44. :compiler-macro)))
  45. (list op-name
  46. op-type
  47. start)))))
  48. `(:ok ,pretty-expansion ,subform-info))))))))
  49. (defun expand-form-once (form compiler-macros? context)
  50. (multiple-value-bind (expansion expanded?)
  51. (macroexpand-1-in-context form context)
  52. (if expanded?
  53. (values expansion nil)
  54. (if (not compiler-macros?)
  55. (values nil "Not a macro form")
  56. (multiple-value-bind (expansion expanded?)
  57. (compiler-macroexpand-1 form)
  58. (if expanded?
  59. (values expansion nil)
  60. (values nil "Not a macro or compiler-macro form")))))))
  61. (defslimefun macro-form-p (string compiler-macros? context)
  62. (with-buffer-syntax ()
  63. (let ((form
  64. (handler-case
  65. (read-from-string string)
  66. (error (condition)
  67. (unless (debug-on-swank-error)
  68. (return-from macro-form-p
  69. `(:error ,(format nil "Read error: ~A" condition))))))))
  70. `(:ok ,(macro-form-type form compiler-macros? context)))))
  71. (defun macro-form-type (form compiler-macros? context)
  72. (cond
  73. ((or (not (consp form))
  74. (not (symbolp (car form))))
  75. nil)
  76. ((multiple-value-bind (expansion expanded?)
  77. (macroexpand-1-in-context form context)
  78. (declare (ignore expansion))
  79. expanded?)
  80. :macro)
  81. ((and compiler-macros?
  82. (multiple-value-bind (expansion expanded?)
  83. (compiler-macroexpand-1 form)
  84. (declare (ignore expansion))
  85. expanded?))
  86. :compiler-macro)
  87. (t
  88. nil)))
  89. ;;;; Hacks to support macro-expansion within local context
  90. (defparameter *macrostep-tag* (gensym))
  91. (defparameter *macrostep-placeholder* '*macrostep-placeholder*)
  92. (define-condition expansion-in-context-failed (simple-error)
  93. ())
  94. (defmacro throw-expansion (form &environment env)
  95. (throw *macrostep-tag* (macroexpand-1 form env)))
  96. (defmacro throw-collected-macro-forms (form &environment env)
  97. (throw *macrostep-tag* (collect-macro-forms form env)))
  98. (defun macroexpand-1-in-context (form context)
  99. (handler-case
  100. (macroexpand-and-catch
  101. `(throw-expansion ,form) context)
  102. (error ()
  103. (macroexpand-1 form))))
  104. (defun collect-macro-forms-in-context (form context)
  105. (handler-case
  106. (macroexpand-and-catch
  107. `(throw-collected-macro-forms ,form) context)
  108. (error ()
  109. (collect-macro-forms form))))
  110. (defun macroexpand-and-catch (form context)
  111. (catch *macrostep-tag*
  112. (macroexpand-all (enclose-form-in-context form context))
  113. (error 'expansion-in-context-failed)))
  114. (defun enclose-form-in-context (form context)
  115. (with-buffer-syntax ()
  116. (destructuring-bind (prefix suffix) context
  117. (let* ((placeholder-form
  118. (read-from-string
  119. (concatenate
  120. 'string
  121. prefix (prin1-to-string *macrostep-placeholder*) suffix)))
  122. (substituted-form (subst form *macrostep-placeholder*
  123. placeholder-form)))
  124. (if (not (equal placeholder-form substituted-form))
  125. substituted-form
  126. (error 'expansion-in-context-failed))))))
  127. ;;;; Tracking Pretty Printer
  128. (defun marker-char-p (char)
  129. (<= #xe000 (char-code char) #xe8ff))
  130. (defun make-marker-char (id)
  131. ;; using the private-use characters U+E000..U+F8FF as markers, so
  132. ;; that's our upper limit for how many we can use.
  133. (assert (<= 0 id #x8ff))
  134. (code-char (+ #xe000 id)))
  135. (defun marker-char-id (char)
  136. (assert (marker-char-p char))
  137. (- (char-code char) #xe000))
  138. (defparameter +whitespace+ (mapcar #'code-char '(9 13 10 32)))
  139. (defun whitespacep (char)
  140. (member char +whitespace+))
  141. (defun pprint-to-string (object &optional pprint-dispatch)
  142. (let ((*print-pprint-dispatch* (or pprint-dispatch *print-pprint-dispatch*)))
  143. (with-bindings *macroexpand-printer-bindings*
  144. (to-string object))))
  145. #-clisp
  146. (defun collect-form-positions (expansion printed-expansion forms)
  147. (loop for (start end)
  148. in (collect-marker-positions
  149. (pprint-to-string expansion (make-tracking-pprint-dispatch forms))
  150. (length forms))
  151. collect (when (and start end)
  152. (list (find-non-whitespace-position printed-expansion start)
  153. (find-non-whitespace-position printed-expansion end)))))
  154. ;; The pprint-dispatch table constructed by
  155. ;; MAKE-TRACKING-PPRINT-DISPATCH causes an infinite loop and stack
  156. ;; overflow under CLISP version 2.49. Make the COLLECT-FORM-POSITIONS
  157. ;; entry point a no-op in thi case, so that basic macro-expansion will
  158. ;; still work (without detection of inner macro forms)
  159. #+clisp
  160. (defun collect-form-positions (expansion printed-expansion forms)
  161. nil)
  162. (defun make-tracking-pprint-dispatch (forms)
  163. (let ((original-table *print-pprint-dispatch*)
  164. (table (copy-pprint-dispatch)))
  165. (flet ((maybe-write-marker (position stream)
  166. (when position
  167. (write-char (make-marker-char position) stream))))
  168. (set-pprint-dispatch 'cons
  169. (lambda (stream cons)
  170. (let ((pos (position cons forms)))
  171. (maybe-write-marker pos stream)
  172. ;; delegate printing to the original table.
  173. (funcall (pprint-dispatch cons original-table)
  174. stream
  175. cons)
  176. (maybe-write-marker pos stream)))
  177. most-positive-fixnum
  178. table))
  179. table))
  180. (defun collect-marker-positions (string position-count)
  181. (let ((positions (make-array position-count :initial-element nil)))
  182. (loop with p = 0
  183. for char across string
  184. unless (whitespacep char)
  185. do (if (marker-char-p char)
  186. (push p (aref positions (marker-char-id char)))
  187. (incf p)))
  188. (map 'list #'reverse positions)))
  189. (defun find-non-whitespace-position (string position)
  190. (loop with non-whitespace-position = -1
  191. for i from 0 and char across string
  192. unless (whitespacep char)
  193. do (incf non-whitespace-position)
  194. until (eql non-whitespace-position position)
  195. finally (return i)))
  196. (provide :swank-macrostep)