我希望获得宏扩展发生位置的文件位置和字符位置,以突出显示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,因为我不知道究竟应该在哪里发生。
答案 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想要对它进行一些检查,你仍然需要一种方法来根据行号和列来解释这些文件位置,但我认为这是一个很好的第一步。