我可以在其扩展站点中获取宏的边界吗?

时间:2016-05-31 09:15:40

标签: common-lisp reader-macro

我希望获得宏扩展发生位置的文件位置和字符位置,以突出显示GUI中的宏扩展。

为此,我希望能够从宏本身引用宏的当前位置。

例如,如果我有以下代码:

(defun mve ()
  (magic-macro :maybe :args))

我希望能够将其扩展为类似

的内容
(defun mve ()
  (progn
    (macro-body-stuff)
    "This expansion took place at #P(myfile.lisp) between chars 16 and 40"))

如果存在这样的功能,最小的示例宏可能是

的内容
(defmacro maybe-macro (&rest r)
  `(progn
     (macro-body-stuff)
     ,(format nil "This expansion took place at ~S between chars ~D and ~D"
             (??:get-macroexpansion-pathname)
             (??:get-macroexpansion-char-start)
             (??:get-macroexpansion-char-end))))

我也将它标记为reader-macro,因为我不知道究竟应该在哪里发生。

1 个答案:

答案 0 :(得分:2)

使用普通宏

无法移植
  

“此扩展发生在#16(myfile.lisp)之间,字符16和40之间”

一般情况下,您将无法获得此类内容,因为一旦阅读完表单后它就无法使用。例如,。如果你有一个包含这个内容的文件:

;; line 0
(some-form arg1)

以及包含此内容的文件:

;; line 0
;; line 1
;; line 2
(


some-form



arg1
                )

从概念上讲,编译器将获得相同的输入。原则上, reader 首先从文件中读取一个表单,然后将其传递给编译器。在这两种情况下,编译器都会获得(某种形式的arg1)形式。这就是你写的宏也保证有访问权限。单个实现可能实际上使编译器可用,但它将以依赖于实现的方式,并且不一定以可移植的方式向您公开。

在加载文件时,文件加载器会绑定一些标准内容,这有助于提供一些此类信息。例如,load函数将特殊变量与文件的路径名和truename绑定在一起:

  

*load-truename*受load加载,以保存正在加载的文件的路径名的名称。

     

*load-pathname*由load绑定,以保存表示与默认值合并的filespec的路径名。也就是(pathname (merge-pathnames filespec))

可以以相同的方式访问提供行和列号等内容的实现相关扩展(如果有)。

但您有时可以使用阅读器宏

来执行此操作

您不能使用普通的宏来移植,因为您没有可移植的机制来确定从中读取表单的位置。但是,读取器宏调用一个函数,该函数使用从中读取表单的流进行调用,并且有用于调查流中位置的函数。例如,这是一个文件:

(defparameter *begin* nil
  "File position before reading a form prefixed with #@.")

(defparameter *end* nil
  "File position after reading a form prefixed with #@.")

(eval-when (:compile-toplevel :load-toplevel :execute)
  (set-dispatch-macro-character
   #\# #\@
   (lambda (stream char infix-parameter)
     (declare (ignore char infix-parameter))
     (let ((begin (file-position stream))
           (form (read stream t nil t))
           (end (file-position stream)))
       `(let ((*begin* ,begin)
              (*end* ,end))
          ,form)))))

(defun foo ()
  #@(format nil "form began at ~a and ended at ~a."
            *begin* *end*))

现在,我们加载后,我们可以调用 foo

CL-USER> (load ".../reader-macro-for-position.lisp")
T
CL-USER> (foo)
"form began at 576 and ended at 650."

当然,这有点脆弱,因为读取器宏可以通过一种方式调用,其中流不是文件位置非常有意义的流,所以你' d想要对它进行一些检查,你仍然需要一种方法来根据行号和列来解释这些文件位置,但我认为这是一个很好的第一步。