文件的编译在 Common Lisp 中定义:CLHS Section 3.2.3 File Compilation
编译时:要使用使用读取宏的表单,您必须使该读取宏实现对编译器可用。
通常,此类依赖关系是使用defsystem
工具处理的,其中描述了系统(类似于项目)的各种文件之间的依赖关系。为了编译某个文件,必须将另一个文件(最好是编译后的版本)加载到编译的 Lisp 中。
现在,如果你想定义 read 宏并在同一个文件中使用它的表示法,那么你再次需要确保编译器知道 read 宏及其实现。文件编译器有编译环境。默认情况下,它不会将同一文件的编译函数加载到此环境中。
为了让编译器知道它编译 Common Lisp 提供的文件中的某些代码EVAL-WHEN
。
让我们看一个读取宏示例:
(set-syntax-from-char #\] #\))
(defun reader-example (stream char)
(declare (ignore char))
(let ((class (read stream t nil t))
(args (read-delimited-list #\] stream t)))
(apply #'make-instance
class
args)))
(set-macro-character #\[ 'reader-example)
(defclass example ()
((name :initarg :name)))
(defvar *examples*
(list [example :name e1]
[example :name e2]
[example :name e3]))
如果你加载上面的源代码,一切都很好。但是如果我们使用文件编译器,它不会在不先加载它的情况下进行编译。COMPILE-FILE
例如,通过使用路径名调用函数来调用文件编译器。
现在编译文件:
(set-syntax-from-char #\] #\))
以上不会在编译时执行。新的语法更改在编译时将不可用。
(defun reader-example (stream char)
(declare (ignore char))
(let ((class (read stream t nil t))
(args (read-delimited-list #\] stream t)))
(apply #'make-instance
class
args)))
上面的函数被编译,但没有被加载。在后面的步骤中,编译器无法使用它的实现。
(set-macro-character #\[ 'reader-example)
同样,上面的表单没有被执行——只是生成了它的代码。
(defclass example ()
((name :initarg :name)))
编译器会记录该类,但以后不能创建它的实例。
(defvar *examples*
(list [example :name e1]
[example :name e2]
[example :name e3]))
上面的代码会触发错误,因为 read 宏在编译时不可用 - 除非它之前已加载。
现在有两个简单的解决方案:
例子:
(EVAL-WHEN (:compile-toplevel :load-toplevel :execute)
(do-something-also-at-compile-time))
上面将被编译器看到并执行。现在您必须确保代码在编译时具有它调用的所有内容(所有需要的定义)。
不用说:尽可能减少这种编译依赖是一种很好的风格。通常将所需的功能放在一个单独的文件中,并确保在编译使用它的文件之前将该文件编译并加载到编译 Lisp 中。