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