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

标签 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))))

我也将它标记为阅读器宏,因为我不知道应该在哪里发生。

最佳答案

你不能用普通的宏来移植

"This expansion took place at #P(myfile.lisp) between chars 16 and 40"

一般情况下,您将无法获得那种东西,因为一旦阅读了表格,它就不再可用了。例如,。如果您有包含此内容的文件:

;; line 0
(some-form arg1)

以及包含此内容的文件:

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


some-form



arg1
                )

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

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

*load-truename* is bound by load to hold the truename of the pathname of the file being loaded.

*load-pathname* is bound by load to hold a pathname that represents filespec merged against the defaults. That is, (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."

这当然有点脆弱,因为可以以一种方式调用读取器宏,其中流不是 file-position 具有很大意义的流,因此您我想对此进行一些检查,您仍然需要一种方法来根据行号和列来解释这些文件位置,但我认为这是一个很好的开端。

关于common-lisp - 我可以在他们的扩展站点中获取宏的边界吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37541578/

相关文章:

在 'rest arguments' 读取器宏之后带有 'discard' 宏的 Clojure 函数文字

emacs - Lisp 源代码文件本身是列表吗?

loops - 在 Common Lisp 中使用循环宏的嵌套循环

macros - lisp defclass宏问题

common-lisp - On Lisp 的 #[ read 宏中引用的必要性?

stream - 更改 common lisp 中的默认阅读器

syntax-error - 常见的 lisp 错误 : "should be lambda expression"

lisp - CL-FAD - defsystem 中的冗余?

lisp - #+: and #-: in common lisp是什么意思

common-lisp - 如何强制 Common Lisp 将数字视为符号名称?