Compiler construction 用read宏编译Lisp代码

Compiler construction 用read宏编译Lisp代码,compiler-construction,lisp,common-lisp,interpreter,reader-macro,Compiler Construction,Lisp,Common Lisp,Interpreter,Reader Macro,当我将一个lisp代码文件编译成字节码或原始程序集(或fasl文件)时,我在理解read宏时遇到了一些困难。或者我确实理解,但不知道。我真的很困惑 当您使用read宏时,您不需要有可用的源代码吗 如果您这样做,那么您必须执行构成读取宏功能的源代码。如果你不这样做,那么当你可以做像读字符这样的事情时,它们又如何工作呢 要做到这一点,如果你想让read宏使用一个重新定义的变量,你必须在它之前执行所有的代码,所以这就变成了运行时,这会把一切都搞糟 如果不在代码之前运行代码,那么上面定义的内容将不可用

当我将一个lisp代码文件编译成字节码或原始程序集(或fasl文件)时,我在理解read宏时遇到了一些困难。或者我确实理解,但不知道。我真的很困惑

当您使用read宏时,您不需要有可用的源代码吗

如果您这样做,那么您必须执行构成读取宏功能的源代码。如果你不这样做,那么当你可以做像读字符这样的事情时,它们又如何工作呢

要做到这一点,如果你想让read宏使用一个重新定义的变量,你必须在它之前执行所有的代码,所以这就变成了运行时,这会把一切都搞糟

如果不在代码之前运行代码,那么上面定义的内容将不可用

那么定义读取宏的函数或编译器宏呢?我假设它们根本不起作用,除非你
需要
加载
一个文件或一些未编译的东西。但是如果它们被编译,那么它们就不能使用它们了

如果我的一些猜测是正确的,那么这意味着“哪些数据将可用于宏”和“哪些宏将可用于函数”之间存在很大的差异,这取决于您是编译整个文件以稍后运行,还是一次解释一行文件(即,逐个读取、编译和计算一个表达式)

简言之,似乎要将一行编译成一种形式,在这种形式下,无需进一步的宏处理或其他什么,就可以执行它,您必须读取、编译和运行前面的行

请再次记住,这些问题适用于编译lisp,而不是在输入每一行代码时对其进行解释

很抱歉,我不太熟悉lisp,我想知道它是如何工作的。

宏(包括读取宏)只不过是函数,它们的处理方式与所有其他函数一样。编译函数或宏后,不需要保留源代码


许多Lisp实现根本不会进行任何解释。例如,默认情况下,SBCL只会编译,即使对于
eval
,也不会切换到解释模式。一个重要的细微差别是,常见的Lisp编译是增量的(与许多Scheme实现和语言(如C和Java)中常见的单独编译相反),它允许您在同一个“编译单元”中编译函数或宏并直接使用.

这实际上是一个有趣的问题,也是很多刚开始使用Lisp的程序员都在努力解决的问题。其中一个主要原因是,一切都“按预期”工作,只有当您开始使用Lisp的更高级功能时,您才真正开始考虑这些问题

你的问题的简短答案是,是的,为了使代码被正确编译,一些以前的代码必须被执行。注意一些单词,这是关键。让我们做一个小例子。考虑一个文件,内容如下:

(print 'a)

(defmacro bar (x) `(print ,x))

(bar 'b)
如您所知,如果在此文件上运行
COMPILE-FILE
,生成的
.fasl
文件将只包含以下代码的编译版本:

(print 'a)
(print 'b)
“但是”,您可能会问,“为什么编译过程中执行了
DEFMACRO
表单,而
PRINT
表单没有执行?”。答案在Hyperspec部分进行了解释。它包含以下句子:

通常,顶级表单出现在用编译的文件中 仅当生成的编译文件为 已加载,而不是在编译文件时加载。但是,通常 文件中的某些表单需要在编译时进行计算的情况 时间,以便正确读取和编译文件的其余部分

有一个表单可以用来精确控制表单的求值时间。为此,您可以使用
EVAL-when
。事实上,这正是Lisp编译器实现
DEFMACRO
本身的方式。您可以通过在REPL中键入以下内容来查看Lisp是如何实现它的:

(macroexpand '(defmacro bar (x) `(print ,x)))

显然,不同的Lisp实现将以不同的方式实现这一点,但关键的是,它将定义包装为一种形式:
(eval when(:compile-toplevel:load-toplevel:execute)…)
。这告诉编译器在编译文件时以及加载文件时都应该对表单求值。如果不这样做,您将无法在定义的同一文件中使用宏。如果仅在编译文件时才对表单求值,您将无法在其他文件中使用宏er加载它。

文件的编译在Common Lisp中定义:

编译时:要使用使用read宏的窗体,必须使编译器可以使用该read宏实现

通常使用
defsystem
工具处理此类依赖关系,其中描述了系统的各种文件(类似于项目)之间的依赖关系。为了编译某个文件,必须将另一个文件(最好是编译版本)加载到编译Lisp中

现在,如果要定义读取宏并在同一个文件中使用其表示法,则需要再次确保编译器了解读取宏及其实现。文件编译器具有编译环境。默认情况下,它不会将同一文件的编译函数加载到此环境中

为了让编译器知道文件中的某些代码,它编译公共Lisp提供了
EVAL-WHEN

让我们看一个read宏示例:

(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]))
如果加载上述源代码,一切正常。但是如果我们使用文件编译器,它在未加载之前不会编译。例如,文件编译器是通过调用函数调用的
(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]))
(EVAL-WHEN (:compile-toplevel :load-toplevel :execute)
  (do-something-also-at-compile-time))