使用读取宏编译Lisp代码

Set*_*gie 7 lisp compiler-construction interpreter common-lisp reader-macro

在将lisp代码文件编译成字节码或原始程序集(或者就此而言的fasl文件)时,我在理解读取宏的内容时遇到了一些麻烦.或许我确实理解但却不知道.我真的很困惑.

当您使用读取宏时,您是否必须拥有可用的源?

如果这样做,那么您必须执行构成读取宏功能的源代码.如果你不这样做,那么当你可以做的事情时,他们怎么能工作read-char

要做到这一点,如果你想让read宏使用上面提到的变量,你必须在它之前执行所有代码,这样就变成了运行时,它会弄乱一切.

如果您之前没有运行代码,那么上面定义的内容将无法使用.

那些定义读取宏的函数或编译器宏呢?除非你require或者load文件或者没有编译的东西,否则我会认为它们根本不起作用.但如果他们被编译,那么他们将无法使用它们?

如果我的一些猜测是正确的,那么这意味着"可用于宏的数据"和"函数可用的宏"存在很大差异,具体取决于您是要编译整个文件以便稍后运行还是一次解释一行文件(即,读取,编译和评估一个接一个的表达式).

简而言之,似乎要将一行编译成一个可以在没有进一步宏处理的情况下执行的表单,或者你需要读取,编译和运行前面的行.

再次记住,这些问题适用于编译lisp,而不是将其解释为可以在每行运行时将其解释.

对不起我的漫无目的,但我是lisp的新手,想知道它是如何工作的.

Eli*_*son 5

这实际上是一个有趣的问题,也是很多Lisp程序员都在努力解决的问题.其中一个主要原因是,所有内容大部分都按预期工作,当您开始使用Lisp的更高级功能时,您才真正开始考虑这些事情.

对你的问题的简短回答是,为了正确编译代码,必须执行一些先前的代码.注意这个词,这是关键.让我们举一个小例子.考虑具有以下内容的文件:

(print 'a)

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

(bar 'b)
Run Code Online (Sandbox Code Playgroud)

正如您已经想到的那样,如果您运行COMPILE-FILE此文件,生成的.fasl文件将只包含以下代码的编译版本:

(print 'a)
(print 'b)
Run Code Online (Sandbox Code Playgroud)

"但是",您可能会问,"为什么DEFMACRO在编译期间执行表单,但PRINT表单不是?".答案在Hyperspec第3.2.3节中解释.它包含以下句子:

通常,只有在加载生成的编译文件时才会评估出现在使用编译文件编译的文件中的顶级表单,而不是在编译文件时.但是,通常情况下需要在编译时评估文件中的某些表单,以便可以正确读取和编译文件的其余部分.

有一个表单可用于精确控制何时评估表单.你EVAL-WHEN用于此目的.实际上,这正是Lisp编译器实现DEFMACRO自身的方式.您可以通过在REPL中键入以下内容来查看Lisp如何实现它:

(macroexpand '(defmacro bar (x) `(print ,x)))
Run Code Online (Sandbox Code Playgroud)

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

  • 编译后的文件不仅包含两个print语句,还包含宏定义. (2认同)

SK-*_*gic 1

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

许多 Lisp 实现根本不会做任何解释。例如,SBCL 默认情况下只会编译,而不会切换到解释模式,即使对于eval. 一个重要的细微差别是,Common Lisp 编译是增量式的(与单独编译相反,这种编译在许多方案实现以及 C 和 Java 等语言中很常见),它允许您编译函数或宏并立即使用它,在同一个“编制单位”。