Path: blob/master/elisp/slime/slime-1.2/swank-source-path-parser.lisp
990 views
;;;; Source-paths12;;; CMUCL/SBCL use a data structure called "source-path" to locate3;;; subforms. The compiler assigns a source-path to each form in a4;;; compilation unit. Compiler notes usually contain the source-path5;;; of the error location.6;;;7;;; Compiled code objects don't contain source paths, only the8;;; "toplevel-form-number" and the (sub-) "form-number". To get from9;;; the form-number to the source-path we need the entire toplevel-form10;;; (i.e. we have to read the source code). CMUCL has already some11;;; utilities to do this translation, but we use some extended12;;; versions, because we need more exact position info. Apparently13;;; Hemlock is happy with the position of the toplevel-form; we also14;;; 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 in18;;; hashtable for later retrieval.19;;;20;;; This code has been placed in the Public Domain. All warranties21;;; are disclaimed.2223;;; Taken from swank-cmucl.lisp, by Helmut Eller2425(in-package :swank-backend)2627;; Some test to ensure the required conformance28(let ((rt (copy-readtable nil)))29(assert (or (not (get-macro-character #\space rt))30(nth-value 1 (get-macro-character #\space rt))))31(assert (not (get-macro-character #\\ rt))))3233(defun make-sharpdot-reader (orig-sharpdot-reader)34#'(lambda (s c n)35;; We want things like M-. to work regardless of any #.-fu in36;; the source file that is to be visited. (For instance, when a37;; file contains #. forms referencing constants that do not38;; currently exist in the image.)39(ignore-errors (funcall orig-sharpdot-reader s c n))))4041(defun make-source-recorder (fn source-map)42"Return a macro character function that does the same as FN, but43additionally stores the result together with the stream positions44before and after of calling FN in the hashtable SOURCE-MAP."45(declare (type function fn))46(lambda (stream char)47(let ((start (1- (file-position stream)))48(values (multiple-value-list (funcall fn stream char)))49(end (file-position stream)))50;(format t "[~D \"~{~A~^, ~}\" ~D ~D ~S]~%" start values end (char-code char) char)51(unless (null values)52(push (cons start end) (gethash (car values) source-map)))53(values-list values))))5455(defun make-source-recording-readtable (readtable source-map)56"Return a source position recording copy of READTABLE.57The source locations are stored in SOURCE-MAP."58(flet ((install-special-sharpdot-reader (*readtable*)59(let ((old-reader (ignore-errors60(get-dispatch-macro-character #\# #\.))))61(when old-reader62(set-dispatch-macro-character #\# #\.63(make-sharpdot-reader old-reader))))))64(let* ((tab (copy-readtable readtable))65(*readtable* tab))66(dotimes (code 128)67(let ((char (code-char code)))68(multiple-value-bind (fn term) (get-macro-character char tab)69(when fn70(set-macro-character char (make-source-recorder fn source-map)71term tab)))))72(install-special-sharpdot-reader tab)73tab)))7475(defun read-and-record-source-map (stream)76"Read the next object from STREAM.77Return the object together with a hashtable that maps78subexpressions of the object to stream positions."79(let* ((source-map (make-hash-table :test #'eq))80(*readtable* (make-source-recording-readtable *readtable* source-map))81(start (file-position stream))82(form (ignore-errors (read stream)))83(end (file-position stream)))84;; ensure that at least FORM is in the source-map85(unless (gethash form source-map)86(push (cons start end) (gethash form source-map)))87(values form source-map)))8889(defun skip-toplevel-forms (n stream)90(let ((*read-suppress* t))91(dotimes (i n)92(read stream))))9394(defun read-source-form (n stream)95"Read the Nth toplevel form number with source location recording.96Return the form and the source-map."97(skip-toplevel-forms n stream)98(let ((*read-suppress* nil))99(read-and-record-source-map stream)))100101(defun source-path-stream-position (path stream)102"Search the source-path PATH in STREAM and return its position."103(check-source-path path)104(destructuring-bind (tlf-number . path) path105(multiple-value-bind (form source-map) (read-source-form tlf-number stream)106(source-path-source-position (cons 0 path) form source-map))))107108(defun check-source-path (path)109(unless (and (consp path)110(every #'integerp path))111(error "The source-path ~S is not valid." path)))112113(defun source-path-string-position (path string)114(with-input-from-string (s string)115(source-path-stream-position path s)))116117(defun source-path-file-position (path filename)118;; We go this long way round, and don't directly operate on the file119;; stream because FILE-POSITION (used above) is not totally savy even120;; on file character streams; on SBCL, FILE-POSITION returns the binary121;; offset, and not the character offset---screwing up on Unicode.122(let ((toplevel-number (first path))123(buffer))124(with-open-file (file filename)125(skip-toplevel-forms (1+ toplevel-number) file)126(let ((endpos (file-position file)))127(setq buffer (make-array (list endpos) :element-type 'character128:initial-element #\Space))129(assert (file-position file 0))130(read-sequence buffer file :end endpos)))131(source-path-string-position path buffer)))132133(defun source-path-source-position (path form source-map)134"Return the start position of PATH from FORM and SOURCE-MAP. All135subforms along the path are considered and the start and end position136of the deepest (i.e. smallest) possible form is returned."137;; compute all subforms along path138(let ((forms (loop for n in path139for f = form then (nth n f)140collect f)))141;; select the first subform present in source-map142(loop for form in (reverse forms)143for positions = (gethash form source-map)144until (and positions (null (cdr positions)))145finally (destructuring-bind ((start . end)) positions146(return (values start end))))))147148149150