我将尝试解释一种简单的方法来编译嵌套
lambdas。由于 Greg 解释将 let 扩展为 lambda
非常好,我根本不会称呼let,我会假设let 是
派生形式或宏,并扩展为 lambda 形式,即
立即调用。
将 Lisp 或 Scheme 函数直接编译成 C 或 C++ 函数
由于其他海报提出的问题,这将是棘手的。根据
这种方法,生成的 C 或 C++ 将无法识别(甚至
非常易读)。
我在完成计算机程序的结构和解释后写了一个 Lisp-to-C 编译器(这是最后的练习之一,实际上我作弊,只是写了一个从 SICP 字节码到 C 的翻译器)。它发出的 C 子集根本没有使用 C 函数来处理 Lisp 函数。这是因为
SICP第5章注册机器语言真的是低级
比 C.
假设您有某种形式的环境,将名称绑定到值,您可以像这样定义函数调用的关键:使用绑定到参数的形式参数扩展定义函数的环境,然后评估这个新环境中的函数体。
在 SICP 的编译器中,环境保存在一个全局变量中,还有其他的
保存函数调用的参数列表的全局变量,如
以及被调用的过程对象(过程对象包括一个指向它被定义的环境的指针),以及一个函数返回时跳转到的标签。
请记住,当您编译 lambda 表达式时,
是您在编译时知道的两个句法组件:
lambda 的参数和正文。
编译函数时,发出的代码类似于
这个伪代码:
some-label
*env* = definition env of *proc*
*env* = extend [formals] *argl* *env*
result of compiling [body]
...
jump *continue*
... 其中*env* 和*argl* 是持有
环境和参数列表,extend 是一些函数(这可以
是一个适当的 C++ 函数),它通过以下方式扩展了环境 *env*
将*argl* 中的名称与[formals] 中的值配对。
然后,当编译后的代码运行时,就会调用这个
lambda 在代码中的其他地方,调用约定是把
将参数列表求值的结果放入*argl*变量中,将返回标签放入*continue*变量中,然后跳转到some-label。
在嵌套lambdas 的情况下,发出的代码看起来像
像这样:
some-label
*env* = definition env of *proc*
*env* = extend [formals-outer] *argl* *env*
another-label
*env* = definition env of *proc*
*env* = extend [formals-inner] *argl* *env*
result of compiling [body-inner]
...
jump *continue*
rest of result of compiling [body-outer]
... somewhere in here there might be a jump to another-label
jump *continue*
这有点难以解释,而且我确定我已经搞糊涂了
它的工作。我想不出一个不涉及我基本上草率地描述 SICP 的整个第 5 章的体面的例子。由于我花了时间写这个答案,所以我会发布它,但如果它令人绝望地令人困惑,我很抱歉。
我强烈推荐SICP 和Lisp in Small Pieces。
SICP 涵盖了面向初学者的元循环解释,以及解释器的许多变体,以及我在上面设法混淆和破坏的字节码编译器。这只是最后两章,前三章也一样好。这是一本很棒的书。如果您还没有阅读,请务必阅读。
LiSP 包含许多用 Scheme 编写的解释器、一个字节码编译器和一个 C 编译器。我在其中,可以自信地说,这是一本内容丰富、内容丰富的书,值得任何感兴趣的人阅读在 Lisp 的实现中。在这一点上它可能有点过时了,但对于像我这样的初学者来说,它仍然很有价值。虽然它比 SICP 更先进,所以要小心。它在中间包含一章关于指称语义的章节,基本上就在我的脑海中。
其他一些注意事项:
Darius Bacon's self-hosting Lisp to C compiler
lambda lifting,这是我认为 Marc Feeley 使用的更高级的技术