[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/macrostep d7991b4 070/110: Track forms using the printer r
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/macrostep d7991b4 070/110: Track forms using the printer rather than the reader |
Date: |
Sat, 7 Aug 2021 09:18:05 -0400 (EDT) |
branch: elpa/macrostep
commit d7991b4a9b6f415085c980d2a64df02618cc3837
Author: Luís Oliveira <loliveira@common-lisp.net>
Commit: Luís Oliveira <loliveira@common-lisp.net>
Track forms using the printer rather than the reader
---
swank-macrostep.lisp | 195 ++++++++++++++++++++++++++-------------------------
1 file changed, 101 insertions(+), 94 deletions(-)
diff --git a/swank-macrostep.lisp b/swank-macrostep.lisp
index c5f835a..d822411 100644
--- a/swank-macrostep.lisp
+++ b/swank-macrostep.lisp
@@ -13,6 +13,11 @@
(in-package #:swank-macrostep)
+(defun pprint-to-string (object &optional pprint-dispatch)
+ (let ((*print-pprint-dispatch* (or pprint-dispatch *print-pprint-dispatch*)))
+ (with-bindings *macroexpand-printer-bindings*
+ (to-string object))))
+
(defun macrostep-expand-1 (string binding-strings &optional compiler-macros?)
(with-buffer-syntax ()
(let* ((form (read-from-string string))
@@ -30,31 +35,35 @@
(if expanded?
expansion
(error "Not a macro or compiler-macro
form.")))))))
- (pretty-expansion (with-bindings *macroexpand-printer-bindings*
- (to-string expansion))))
- (list pretty-expansion
- (multiple-value-bind (expansion* tracking-stream)
- (tracking-read-from-string pretty-expansion)
- (multiple-value-bind (macros compiler-macros)
- (collect-macro-forms expansion*)
- (flet ((collect-positions (forms type)
- (loop for form in forms
- for bounds = (cdr (assoc form (forms-of
tracking-stream)))
- when bounds
- collect (destructuring-bind (start end)
- bounds
- ;; this assumes that the operator
- ;; starts right next to the opening
- ;; parenthesis. I guess we could be
- ;; more forgiving with some
- ;; cleverness on the Emacs side.
- (let ((op-end (+ start (length
(to-string (first form))))))
- (list type
- start (position-line start
tracking-stream)
- op-end (position-line
op-end tracking-stream)
- end (position-line end
tracking-stream)))))))
- (append (collect-positions macros :macro)
- (collect-positions compiler-macros
:compiler-macro)))))))))
+ (pretty-expansion (pprint-to-string expansion)))
+ (multiple-value-bind (macros compiler-macros)
+ (collect-macro-forms expansion)
+ (let* ((all-macros (append macros compiler-macros))
+ (positions (collect-form-positions expansion
+ pretty-expansion
+ all-macros)))
+ (list pretty-expansion
+ (loop for form in all-macros
+ for (start end) in positions
+ ;; this assumes that the operator starts right
+ ;; next to the opening parenthesis. We could
+ ;; probably be more forgiving.
+ for op-end = (+ start (length (to-string (first form))))
+ collect (list
+ (if (member form macros) :macro :compiler-macro)
+ start (position-line start pretty-expansion)
+ op-end (position-line op-end pretty-expansion)
+ end (position-line end pretty-expansion)))))))))
+
+(defun position-line (position string)
+ (let ((line 0)
+ (last-newline-position 0))
+ (loop for i upto position
+ for char across string
+ when (eql char #\Newline)
+ do (incf line)
+ (setq last-newline-position i))
+ line))
(defun macro-form-p (string binding-strings &optional compiler-macros?)
(with-buffer-syntax ()
@@ -124,74 +133,72 @@
form))
(values macro-forms compiler-macro-forms)))
-;;;; FORM-TRACKING-STREAM
-
-(defclass form-tracking-stream (swank/gray:fundamental-character-input-stream)
- (;; The underlying stream.
- (source :initarg :source :accessor source-of)
- (position :initform 0 :accessor position-of)
- ;; Track the position of each #\Newline that occurred so that, if
- ;; desired, a line/column can be calculated for any position.
- (newlines :initform (make-array 10 :adjustable t :fill-pointer 0)
- :accessor newlines-of)
- (forms :initform nil :accessor forms-of)))
-
-(defun %read-char (reader stream)
- (handler-case
- (let ((pos (position-of stream))
- (result (funcall reader (source-of stream))))
- (when result
- (incf (position-of stream))
- (when (eql result #\Newline)
- (let* ((newlines (newlines-of stream))
- (n (length newlines)))
- (when (or (zerop n) (> pos (aref newlines (1- n))))
- (vector-push-extend pos newlines))))
- result))
- (end-of-file () :eof)))
-
-(defmethod swank/gray:stream-read-char ((stream form-tracking-stream))
- (%read-char #'read-char stream))
-
-(defmethod swank/gray:stream-read-char-no-hang ((stream form-tracking-stream))
- (%read-char #'read-char-no-hang stream))
-
-(defmethod swank/gray:stream-unread-char ((stream form-tracking-stream)
character)
- (prog1 (unread-char character (source-of stream))
- (decf (position-of stream))))
-
-(defun annotate-position (stream object start)
- (push (list object start (position-of stream))
- (forms-of stream)))
-
-(defun form-tracking-stream-p (stream)
- (typep stream 'form-tracking-stream))
-
-(defun position-line (position tracking-stream)
- (or (position-if (lambda (newline-pos)
- (> newline-pos position))
- (newlines-of tracking-stream))
- (length (newlines-of tracking-stream))))
-
-(defun tracking-read-from-string (string &key (readtable *readtable*))
- (with-input-from-string (string-stream string)
- (let ((instrumented-readtable (copy-readtable readtable))
- (tracking-stream (make-instance 'form-tracking-stream
- :source string-stream)))
- ;; we could do this for every readtable char, using
- ;; named-readtables::do-readtable, but for our purposes here,
- ;; #\( suffices.
- (multiple-value-bind (fn non-terminating-p)
- (get-macro-character #\( readtable)
- (set-macro-character #\(
- (lambda (&rest args)
- (let ((start (1- (position-of tracking-stream)))
- (object (apply fn args)))
- (annotate-position tracking-stream object
start)
- object))
- non-terminating-p
- instrumented-readtable))
- (let ((*readtable* instrumented-readtable))
- (values (read tracking-stream) tracking-stream)))))
+;;;; Tracking Pretty Printer
+
+(defun marker-char-p (char)
+ (<= #xe000 (char-code char) #xe8ff))
+
+(defun make-marker-char (position)
+ ;; using the private-use characters U+E000..U+F8FF as markers, so
+ ;; that's our upper limit for how many we can use.
+ (assert (<= position #x8ff))
+ (code-char (+ #xe000 position)))
+
+(defun marker-char-position (char)
+ (assert (marker-char-p char))
+ (- (char-code char) #xe000))
+
+(defun make-tracking-pprint-dispatch (forms)
+ (let ((original-table *print-pprint-dispatch*)
+ (table (copy-pprint-dispatch)))
+ (flet ((maybe-write-marker (position stream)
+ (when position
+ (write-char (make-marker-char position) stream))))
+ (set-pprint-dispatch 'cons
+ (lambda (stream cons)
+ (let ((pos (position cons forms)))
+ (maybe-write-marker pos stream)
+ ;; delegate printing to the original table.
+ (funcall (pprint-dispatch cons original-table)
+ stream
+ cons)
+ (maybe-write-marker pos stream)))
+ most-positive-fixnum
+ table))
+ table))
+
+(defparameter +whitespace+ (mapcar #'code-char '(9 13 10 32)))
+
+(defun whitespacep (char)
+ (member char +whitespace+))
+
+(defun collect-marker-positions (string position-count)
+ (let ((positions (make-array position-count :initial-element nil)))
+ (loop with p = 0
+ for char across string
+ unless (whitespacep char)
+ do (if (marker-char-p char)
+ (push p (aref positions (marker-char-position char)))
+ (incf p)))
+ (map 'list #'reverse positions)))
+
+(defun collect-form-positions (expansion printed-expansion forms)
+ (let* ((annotated-output
+ (pprint-to-string expansion
+ (make-tracking-pprint-dispatch forms)))
+ (marker-positions
+ (collect-marker-positions annotated-output (length forms))))
+ (loop with i = -1 and non-whitespace-position = -1
+ for (start end) in marker-positions
+ collect (flet ((find-next (position)
+ (loop until (or (eql non-whitespace-position
position)
+ (= (1- (length printed-expansion))
+ (1+ i)))
+ unless (whitespacep (char printed-expansion
+ (incf i)))
+ do (incf non-whitespace-position))
+ i))
+ (list (find-next start)
+ (find-next end))))))
(provide :swank-macrostep)
- [nongnu] branch elpa/macrostep created (now 424e373), ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep d847fb2 001/110: Initial upload to github, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 0067091 018/110: fix youtube link in docs, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep ee46132 008/110: Print dotted lists in expansions correctly, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 99d2cc7 020/110: Don't fontify a quoted macro since this won't get expanded normally, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep f8f0424 027/110: Fix header dates, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 9a534df 052/110: Make macrostep-slime-insert more robust, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 352b6d2 042/110: Minor improvements to SLIME prototype, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 3062d4c 046/110: Add support for compiler macros, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 874c790 058/110: WIP: smarter SLIME macroexpansion, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep d7991b4 070/110: Track forms using the printer rather than the reader,
ELPA Syncer <=
- [nongnu] elpa/macrostep dd14d5c 077/110: Remove unused function `bindings-to-environment`, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep b1c1230 079/110: More tests, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 4b382cd 088/110: Identify Elisp compiler-macros more selectively, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep a3338d3 104/110: Make test script exit non-zero on failure, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep e537612 106/110: compmiler-macro changed to compiler-macro, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep a5b980e 035/110: Update readme, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep fbd61eb 030/110: Tests for macrolet/cl-macrolet support, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep afed3cf 072/110: MARKER-CHAR-POSITION => MARKER-CHAR-ID, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 2390cec 075/110: slime-sexp-at-point may return nil, deal with it, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 2c5bda2 090/110: Update README, bump version number, ELPA Syncer, 2021/08/07