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.

239 lines
9.1 KiB

5 years ago
  1. ;;;; Source-paths
  2. ;;; CMUCL/SBCL use a data structure called "source-path" to locate
  3. ;;; subforms. The compiler assigns a source-path to each form in a
  4. ;;; compilation unit. Compiler notes usually contain the source-path
  5. ;;; of the error location.
  6. ;;;
  7. ;;; Compiled code objects don't contain source paths, only the
  8. ;;; "toplevel-form-number" and the (sub-) "form-number". To get from
  9. ;;; the form-number to the source-path we need the entire toplevel-form
  10. ;;; (i.e. we have to read the source code). CMUCL has already some
  11. ;;; utilities to do this translation, but we use some extended
  12. ;;; versions, because we need more exact position info. Apparently
  13. ;;; Hemlock is happy with the position of the toplevel-form; we also
  14. ;;; need the position of subforms.
  15. ;;;
  16. ;;; We use a special readtable to get the positions of the subforms.
  17. ;;; The readtable stores the start and end position for each subform in
  18. ;;; hashtable for later retrieval.
  19. ;;;
  20. ;;; This code has been placed in the Public Domain. All warranties
  21. ;;; are disclaimed.
  22. ;;; Taken from swank-cmucl.lisp, by Helmut Eller
  23. (defpackage swank/source-path-parser
  24. (:use cl)
  25. (:export
  26. read-source-form
  27. source-path-string-position
  28. source-path-file-position
  29. source-path-source-position
  30. sexp-in-bounds-p
  31. sexp-ref)
  32. (:shadow ignore-errors))
  33. (in-package swank/source-path-parser)
  34. ;; Some test to ensure the required conformance
  35. (let ((rt (copy-readtable nil)))
  36. (assert (or (not (get-macro-character #\space rt))
  37. (nth-value 1 (get-macro-character #\space rt))))
  38. (assert (not (get-macro-character #\\ rt))))
  39. (eval-when (:compile-toplevel)
  40. (defmacro ignore-errors (&rest forms)
  41. ;;`(progn . ,forms) ; for debugging
  42. `(cl:ignore-errors . ,forms)))
  43. (defun make-sharpdot-reader (orig-sharpdot-reader)
  44. (lambda (s c n)
  45. ;; We want things like M-. to work regardless of any #.-fu in
  46. ;; the source file that is to be visited. (For instance, when a
  47. ;; file contains #. forms referencing constants that do not
  48. ;; currently exist in the image.)
  49. (ignore-errors (funcall orig-sharpdot-reader s c n))))
  50. (defun make-source-recorder (fn source-map)
  51. "Return a macro character function that does the same as FN, but
  52. additionally stores the result together with the stream positions
  53. before and after of calling FN in the hashtable SOURCE-MAP."
  54. (lambda (stream char)
  55. (let ((start (1- (file-position stream)))
  56. (values (multiple-value-list (funcall fn stream char)))
  57. (end (file-position stream)))
  58. #+(or)
  59. (format t "[~D \"~{~A~^, ~}\" ~D ~D ~S]~%"
  60. start values end (char-code char) char)
  61. (when values
  62. (destructuring-bind (&optional existing-start &rest existing-end)
  63. (car (gethash (car values) source-map))
  64. ;; Some macros may return what a sub-call to another macro
  65. ;; produced, e.g. "#+(and) (a)" may end up saving (a) twice,
  66. ;; once from #\# and once from #\(. If the saved form
  67. ;; is a subform, don't save it again.
  68. (unless (and existing-start existing-end
  69. (<= start existing-start end)
  70. (<= start existing-end end))
  71. (push (cons start end) (gethash (car values) source-map)))))
  72. (values-list values))))
  73. (defun make-source-recording-readtable (readtable source-map)
  74. (declare (type readtable readtable) (type hash-table source-map))
  75. "Return a source position recording copy of READTABLE.
  76. The source locations are stored in SOURCE-MAP."
  77. (flet ((install-special-sharpdot-reader (rt)
  78. (let ((fun (ignore-errors
  79. (get-dispatch-macro-character #\# #\. rt))))
  80. (when fun
  81. (let ((wrapper (make-sharpdot-reader fun)))
  82. (set-dispatch-macro-character #\# #\. wrapper rt)))))
  83. (install-wrappers (rt)
  84. (dotimes (code 128)
  85. (let ((char (code-char code)))
  86. (multiple-value-bind (fun nt) (get-macro-character char rt)
  87. (when fun
  88. (let ((wrapper (make-source-recorder fun source-map)))
  89. (set-macro-character char wrapper nt rt))))))))
  90. (let ((rt (copy-readtable readtable)))
  91. (install-special-sharpdot-reader rt)
  92. (install-wrappers rt)
  93. rt)))
  94. ;; FIXME: try to do this with *READ-SUPPRESS* = t to avoid interning.
  95. ;; Should be possible as we only need the right "list structure" and
  96. ;; not the right atoms.
  97. (defun read-and-record-source-map (stream)
  98. "Read the next object from STREAM.
  99. Return the object together with a hashtable that maps
  100. subexpressions of the object to stream positions."
  101. (let* ((source-map (make-hash-table :test #'eq))
  102. (*readtable* (make-source-recording-readtable *readtable* source-map))
  103. (*read-suppress* nil)
  104. (start (file-position stream))
  105. (form (ignore-errors (read stream)))
  106. (end (file-position stream)))
  107. ;; ensure that at least FORM is in the source-map
  108. (unless (gethash form source-map)
  109. (push (cons start end) (gethash form source-map)))
  110. (values form source-map)))
  111. (defun starts-with-p (string prefix)
  112. (declare (type string string prefix))
  113. (not (mismatch string prefix
  114. :end1 (min (length string) (length prefix))
  115. :test #'char-equal)))
  116. (defun extract-package (line)
  117. (declare (type string line))
  118. (let ((name (cadr (read-from-string line))))
  119. (find-package name)))
  120. #+(or)
  121. (progn
  122. (assert (extract-package "(in-package cl)"))
  123. (assert (extract-package "(cl:in-package cl)"))
  124. (assert (extract-package "(in-package \"CL\")"))
  125. (assert (extract-package "(in-package #:cl)")))
  126. ;; FIXME: do something cleaner than this.
  127. (defun readtable-for-package (package)
  128. ;; KLUDGE: due to the load order we can't reference the swank
  129. ;; package.
  130. (funcall (read-from-string "swank::guess-buffer-readtable")
  131. (string-upcase (package-name package))))
  132. ;; Search STREAM for a "(in-package ...)" form. Use that to derive
  133. ;; the values for *PACKAGE* and *READTABLE*.
  134. ;;
  135. ;; IDEA: move GUESS-READER-STATE to swank.lisp so that all backends
  136. ;; use the same heuristic and to avoid the need to access
  137. ;; swank::guess-buffer-readtable from here.
  138. (defun guess-reader-state (stream)
  139. (let* ((point (file-position stream))
  140. (pkg *package*))
  141. (file-position stream 0)
  142. (loop for line = (read-line stream nil nil) do
  143. (when (not line) (return))
  144. (when (or (starts-with-p line "(in-package ")
  145. (starts-with-p line "(cl:in-package "))
  146. (let ((p (extract-package line)))
  147. (when p (setf pkg p)))
  148. (return)))
  149. (file-position stream point)
  150. (values (readtable-for-package pkg) pkg)))
  151. (defun skip-whitespace (stream)
  152. (peek-char t stream nil nil))
  153. ;; Skip over N toplevel forms.
  154. (defun skip-toplevel-forms (n stream)
  155. (let ((*read-suppress* t))
  156. (dotimes (i n)
  157. (read stream))
  158. (skip-whitespace stream)))
  159. (defun read-source-form (n stream)
  160. "Read the Nth toplevel form number with source location recording.
  161. Return the form and the source-map."
  162. (multiple-value-bind (*readtable* *package*) (guess-reader-state stream)
  163. (skip-toplevel-forms n stream)
  164. (read-and-record-source-map stream)))
  165. (defun source-path-stream-position (path stream)
  166. "Search the source-path PATH in STREAM and return its position."
  167. (check-source-path path)
  168. (destructuring-bind (tlf-number . path) path
  169. (multiple-value-bind (form source-map) (read-source-form tlf-number stream)
  170. (source-path-source-position (cons 0 path) form source-map))))
  171. (defun check-source-path (path)
  172. (unless (and (consp path)
  173. (every #'integerp path))
  174. (error "The source-path ~S is not valid." path)))
  175. (defun source-path-string-position (path string)
  176. (with-input-from-string (s string)
  177. (source-path-stream-position path s)))
  178. (defun source-path-file-position (path filename)
  179. ;; We go this long way round, and don't directly operate on the file
  180. ;; stream because FILE-POSITION (used above) is not totally savy even
  181. ;; on file character streams; on SBCL, FILE-POSITION returns the binary
  182. ;; offset, and not the character offset---screwing up on Unicode.
  183. (let ((toplevel-number (first path))
  184. (buffer))
  185. (with-open-file (file filename)
  186. (skip-toplevel-forms (1+ toplevel-number) file)
  187. (let ((endpos (file-position file)))
  188. (setq buffer (make-array (list endpos) :element-type 'character
  189. :initial-element #\Space))
  190. (assert (file-position file 0))
  191. (read-sequence buffer file :end endpos)))
  192. (source-path-string-position path buffer)))
  193. (defgeneric sexp-in-bounds-p (sexp i)
  194. (:method ((list list) i)
  195. (< i (loop for e on list
  196. count t)))
  197. (:method ((sexp t) i) nil))
  198. (defgeneric sexp-ref (sexp i)
  199. (:method ((s list) i) (elt s i)))
  200. (defun source-path-source-position (path form source-map)
  201. "Return the start position of PATH from FORM and SOURCE-MAP. All
  202. subforms along the path are considered and the start and end position
  203. of the deepest (i.e. smallest) possible form is returned."
  204. ;; compute all subforms along path
  205. (let ((forms (loop for i in path
  206. for f = form then (if (sexp-in-bounds-p f i)
  207. (sexp-ref f i))
  208. collect f)))
  209. ;; select the first subform present in source-map
  210. (loop for form in (nreverse forms)
  211. for ((start . end) . rest) = (gethash form source-map)
  212. when (and start end (not rest))
  213. return (return (values start end)))))