Common lisp 我可以在他们的扩展站点中获取宏的边界吗?

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")

我想知道宏扩展发生的文件位置和字符位置,以便在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))))
我还将其标记为读卡器宏,因为我不知道应该在何处执行此操作。

使用普通宏无法便携执行此操作 “此扩展发生在字符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)中读取)会得到什么结果
,也就是说,您不是在读取文件,因此文件位置可能没有那么重要。