Common lisp 我可以在他们的扩展站点中获取宏的边界吗?
我想知道宏扩展发生的文件位置和字符位置,以便在GUI中突出显示宏扩展 为此,我希望能够引用宏的当前位置,其中宏本身进行了扩展 例如,如果我有以下代码:Common lisp 我可以在他们的扩展站点中获取宏的边界吗?,common-lisp,reader-macro,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")
(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))))
我还将其标记为读卡器宏,因为我不知道应该在何处执行此操作。使用普通宏无法便携执行此操作
“此扩展发生在字符16和40之间的#p(myfile.lisp)上”
一般来说,您将无法获取此类内容,因为一旦表单被读取,它就不可用。例如,。如果您有包含此内容的文件:
;; line 0
(some-form arg1)
;; line 0
;; line 1
;; line 2
(
some-form
arg1
)
以及包含以下内容的文件:
;; line 0
(some-form arg1)
;; line 0
;; line 1
;; line 2
(
some-form
arg1
)
从概念上讲,编译器将获得相同的输入。原则上,读取器首先从文件中读取表单,然后将其传递给编译器。在这两种情况下,编译器都会获得(某些表单arg1)。这就是您编写的宏保证也可以访问的内容。一个单独的实现实际上可能使编译器有更多的可用性,但它将以依赖于实现的方式提供,并且不一定以可移植的方式提供给您
不过,文件加载器在加载文件时绑定了一些标准内容,这些内容有助于提供这些信息。例如,该函数使用文件的路径名和truename绑定特殊变量:
由load绑定以保存正在加载的文件的路径名的truename
由load绑定以保存一个路径名,该路径名表示根据默认值合并的文件规范。也就是说,(路径名(合并路径名filespec))
提供行号和列号(如果有的话)之类的依赖于实现的扩展也可以用同样的方式访问
但有时可以使用reader宏来执行此操作
使用普通的可移植宏无法做到这一点,因为您没有可移植的机制来确定表单在文件中的读取位置。但是,读取器宏调用一个函数,该函数通过从中读取表单的流被调用,并且有一些函数用于调查流中的位置。例如,这里有一个文件:
(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."
这当然有点脆弱,因为调用reader宏的方式可能会使流的文件位置不太合理,因此您需要对此进行一些检查,并且您仍然需要一种方法来根据行号和列解释这些文件位置,但我认为这是一个很好的第一步。对于Slime,如果您编译的代码包含在编译时发出错误信号的宏,那么您会收到一个编译器注释,并且您的代码会以红色下划线。为了实现这一点,swank.lisp和slime.el中有很多代码可以从lisp中获取依赖于实现的位置信息。您可能会看到它()“依赖于实现的位置信息”——这是要点。它是不可移植的。“因为一旦表单被读取,它就不可用”——在表单被读取后,这可能是正确的,就像将它传递给
eval
或在函数/lambda中传递给compile
,但是编译文件和加载可以有一个专门的读取器,或者访问实现读取器的内部,标记当前文件以及每个表单开始和结束的位置,以便以后使用和/或存储为调试信息。@acelent是的,正如我所说,“因此位置信息不一定可用。也就是说,实现可能提供了一些特殊变量来帮助获取这些信息。”每周的实现可能会有这种情况,但它不是一个标准化的接口。“编译器实际上将获得相同的输入。读取器首先从文件中读取表单,然后将其传递给编译器。在这两种情况下,编译器都会获得表单(某些表单arg1)。因此位置信息不一定可用。”--按照您的说法,您似乎在说明编译器提供了一个没有起始和结束位置信息的表单(强调我的表单),并且您似乎只是指标准的*load truename*
和*load pathname*
(根据规范,实现必须在加载时绑定它们)。@acelent我明白你的意思;我已经更新了答案中的措辞。@关于没有意义的事情,我的意思是我不确定在CL-USER>(eval(从字符串“#@(list*begin**end*)”=>(22)中读取)会得到什么结果
,也就是说,您不是在读取文件,因此文件位置可能没有那么重要。