我尝试设计一个非递归的Scheme解释器,使用堆栈和指针在AST上行走并进行评估。
如果我只需要处理纯过程调用,情况就好了。但是,一旦宏出现,不规则的语法就很难编写非递归例程。 (因为不同语义的混合)更糟糕的是,当考虑内置宏(如if,conf let等)时,非递归方法似乎变得异常复杂。
关于实现非递归解释器的任何好建议?或者那些材料?我用Google搜索但没有找到任何东西。
而且,我想知道主流的Scheme解释器是否使用这种方法。也许我只能写递归而且不会受到指责。
答案 0 :(得分:6)
在vanilla r5rs方案中,宏只是用于重新排列AST的DSL。它们在纯粹的语法层面上运作,应该与解释器分开。
在R6RS或CL等等中,宏实际上可以进行计算,这意味着它们需要2次运行解释器,一次用于扩展宏,另一次用于评估生成的AST。
例如,给定此代码
(let ((x 5))
(if (= x 5)
(display "Woohoo")
(error)))
你应该在第一阶段运行一个宏扩展器,离开AST
((lambda (x)
(cond
((= x 5) (display "Woohoo"))
(else (error)))) 5)
执行此操作应评估无代码。只是重新安排AST。然后当你的解释器运行它时,它甚至不必知道存在宏。
所以你的最终计划翻译应该是这样的
Parse File
|
|
|
Expand All Macros
|
|
|
Optimize/Black Magic
|
|
|
Optional ByteCode compilation or similar IL
|
|
Evaluate
|
|
Profit
答案 1 :(得分:4)
我正在研究我的Scheme编译器,我已经阅读了很多关于编译相关的各种旧问题的论文。与宏和@ jozefg相关的分离我发现的实际解释和宏观扩展的好例子是Alexpander,由Al Petrofsky编写。他还编写了Eval in one define,这是一个很好的语法规则解释器。
我之前写过Lisp1 interpreter that runs on Brainf*ck。它有一个堆栈,交替的单元格地址设置为汽车!结果和要评估的表达式。
Eval是这样的:
(pop_stack register1) ; expression
(while-not-zero register1
... do computation
(pop_stack register2) ; return address
(open-cons register2) ; opens location
(set-car register1) ; sets value
(pop_stack register1)) ; next job
它支持标准的McCharty LISP1,包括宏(flambda)。
像(cons'a'b)这样的简单表达式在while循环中使用了6轮,如下所示:
因此我可以重命名每个关键字。例如。这有效:
(let ((mylambda lambda))
(mylambda (x) (cons '1 x)))