branch: elpa/macrostep
commit ff3888f9abf80101e4ed932650cc2a831819abaa
Author: Luís Oliveira <[email protected]>
Commit: Luís Oliveira <[email protected]>

    Use a form-tracking stream rather than gensym substitution
---
 macrostep.el         |  46 +++++++----------
 swank-macrostep.lisp | 137 ++++++++++++++++++++++++++++++++++-----------------
 2 files changed, 112 insertions(+), 71 deletions(-)

diff --git a/macrostep.el b/macrostep.el
index c8f8556..39c9954 100644
--- a/macrostep.el
+++ b/macrostep.el
@@ -952,36 +952,28 @@ sub-forms.  See also `macrostep-sexp-at-point'."
 
 (defun macrostep-slime-insert (result)
   "Insert RESULT at point, indenting to match the current column."
-  (cl-destructuring-bind (expansion substitutions) result
+  (cl-destructuring-bind (expansion positions) result
     (let* ((indent-string (concat "\n" (make-string (current-column) ? )))
            (expansion (replace-regexp-in-string "\n" indent-string expansion))
-           (start (point)))
+           (start (point))
+           (column-offset (current-column)))
       (insert expansion)
-      (macrostep-slime--propertize-macros start (point) substitutions))))
-
-(defun macrostep-slime--propertize-macros (start end substitutions)
-  "Put text properties on macro forms between START and END."
-  (when substitutions
-    (let ((regexp
-           (rx-to-string
-            `(: (submatch "(")
-                (submatch
-                 (or ,@(mapcar #'car substitutions)))))))
-      (save-excursion
-        (goto-char start)
-        (while (search-forward-regexp regexp end t)
-          (pcase (assoc (match-string 2) substitutions)
-            (`(,_ ,original-symbol ,type)
-              (setq end (+ end (- (length original-symbol)
-                                  (length (match-string 2)))))
-              (replace-match original-symbol t t nil 2)
-              (put-text-property (match-beginning 1) (match-end 1)
-                                 'macrostep-macro-start t)
-              (put-text-property (match-beginning 2) (match-end 2)
-                                 'font-lock-face
-                                 (if (eq type :macro)
-                                     'macrostep-macro-face
-                                   'macrostep-compiler-macro-face)))))))))
+      (macrostep-slime--propertize-macros start column-offset positions))))
+
+(defun macrostep-slime--propertize-macros (start-offset column-offset 
positions)
+  "Put text properties on macro forms."
+  (dolist (position positions)
+    (destructuring-bind (type start start-line op-end op-end-line end end-line)
+        position
+      (put-text-property (+ start-offset start (* column-offset start-line))
+                         (+ start-offset end (* column-offset end-line))
+                         'macrostep-macro-start t)
+      (put-text-property (+ 1 start-offset start (* column-offset start-line))
+                         (+ 1 start-offset op-end (* column-offset 
op-end-line))
+                         'font-lock-face
+                         (if (eq type :macro)
+                             'macrostep-macro-face
+                             'macrostep-compiler-macro-face)))))
 
 (defun macrostep-slime-macro-form-p (string)
   (slime-eval
diff --git a/swank-macrostep.lisp b/swank-macrostep.lisp
index dde3555..d954687 100644
--- a/swank-macrostep.lisp
+++ b/swank-macrostep.lisp
@@ -27,15 +27,30 @@
                       (compiler-macroexpand-1 form env)
                     (if expanded?
                         expansion
-                      (error "Not a macro or compiler-macro form."))))))))
-      (multiple-value-bind (result substitutions)
-          (substitute-macros expansion)
-        (list
-         (to-string result)
-         (loop for (gensym original type) in substitutions
-            collect (list (to-string gensym)
-                          (to-string original)
-                          type)))))))
+                        (error "Not a macro or compiler-macro form.")))))))
+           (pretty-expansion (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)
+                         (mapcar (lambda (form)
+                                   (destructuring-bind (start end)
+                                       (cdr (assoc form (forms-of 
tracking-stream)))
+                                     ;; 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)))))
+                                 forms)))
+                  (append (collect-positions macros :macro)
+                          (collect-positions compiler-macros 
:compiler-macro)))))))))
 
 (defun macro-form-p (string binding-strings &optional compiler-macros?)
   (with-buffer-syntax ()
@@ -70,11 +85,6 @@
           (subexpression (make-environment-extractor (cdr binding-lists))))
       `(macrolet ,binding-list ,subexpression))))
 
-(defun substitute-macros (form)
-  (multiple-value-bind (macro-forms compiler-macro-forms)
-      (collect-macro-forms form)
-    (substitute-macro-forms form macro-forms compiler-macro-forms)))
-
 #-sbcl
 (defun collect-macro-forms (form &optional environment)
   (let ((real-macroexpand-hook *macroexpand-hook*))
@@ -110,35 +120,74 @@
        form))
     (values macro-forms compiler-macro-forms)))
 
-(defun substitute-macro-forms (form macro-forms compiler-macro-forms)
-  (labels
-      ((macro-form? (form)
-         (member form macro-forms :test 'eq))
-       (compiler-macro-form? (form)
-         (member form compiler-macro-forms :test 'eq))
-       (recur (form)
-         (let (macro? compiler-macro?)
-           (cond ((not (consp form))
-                  (values form nil))
-                 ((or
-                   (setf macro? (macro-form? form))
-                   (setf compiler-macro? (compiler-macro-form? form)))
-                  (multiple-value-bind (rest replacements)
-                      (recur (cdr form))
-                    (let ((replacement (gensym)))
-                      (values
-                       (cons replacement rest)
-                       (cons (list replacement (car form)
-                                   (if macro? :macro :compiler-macro))
-                             replacements)))))
-                 (t
-                  (multiple-value-bind (car replacements-1)
-                      (recur (car form))
-                    (multiple-value-bind (cdr replacements-2)
-                        (recur (cdr form))
-                      (values
-                       (cons car cdr)
-                       (append replacements-1 replacements-2)))))))))
-    (recur form)))
+;;;; 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)))
+
+(defmethod swank/gray:stream-read-char ((stream form-tracking-stream))
+  (handler-case
+      (let ((pos (position-of stream))
+            (result (read-char (source-of stream))))
+        (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-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 line-and-column (position tracking-stream)
+  (let* ((line (or (position-if (lambda (newline-pos)
+                                  (> newline-pos position))
+                                (newlines-of tracking-stream))
+                   1))
+         (column (if (eql line 1)
+                     position
+                     (- position (aref (newlines-of tracking-stream) (1- 
line))))))
+    (values line column)))
+
+(defun position-line (position tracking-stream)
+  (nth-value 0 (line-and-column position 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)))))
 
 (provide :swank-macrostep)

Reply via email to