为了在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))))
我也把它标记为阅读器宏,因为我不知道这应该发生在哪里。
使用普通宏无法实现这一点
"此扩展发生在#p(myfile.lisp)字符16和40之间">
通常情况下,您将无法获得此类内容,因为一旦阅读了表单,它就不可用。例如,如果你有一个包含以下内容的文件:
;; line 0
(some-form arg1)
和一个包含以下内容的文件:
;; line 0
;; line 1
;; line 2
(
some-form
arg1
)
从概念上讲,编译器将获得相同的输入。原则上,读取器首先从文件中读取表单,然后将其传递给编译器。在这两种情况下,编译器都会得到形式(某些形式为arg1)。这就是您编写的宏也保证可以访问的内容。一个单独的实现实际上可能会让编译器获得更多的可用性,但它将以一种依赖于实现的方式,不一定会以可移植的方式向您公开。
文件加载器在加载文件时绑定了一些标准内容,这有助于提供一些信息。例如,load函数将特殊变量与文件的路径名和真名绑定:
*load truename*由load绑定,以保存正在加载的文件的路径名的truename。
*load pathname*由load绑定以保存一个路径名,该路径名表示根据默认值合并的文件规范。即
(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."
当然,这有点脆弱,因为读取器宏的调用方式可能是流不是文件位置非常有意义的流,所以你需要对此进行一些检查,并且你仍然需要一种方法来根据行号和列来解释这些文件位置,但我认为这是一个很好的第一步。