在为另一种语言构建解释器时,通常建议创建一个基于堆栈的虚拟机,它可以解释实际解释器生成的字节码。然后,解释器将由两部分组成:转换器,它将指令从高级语言转换为虚拟机的字节码,以及虚拟机本身。
我的问题是:解释语言有哪些替代方案?例如,是否可以(并且实际)跳过虚拟机,并使用C中的函数实现所有指令?在某种程度上,在我看来这应该是可能的,但也许你最终会为了更复杂的功能而实现某种最小的VM。还有其他选择吗?
答案 0 :(得分:2)
建议制作基于堆栈的VM,因为它们很容易制作。
另一种常见类型的VM是基于寄存器的,其中值存储在寄存器中而不是堆栈中。
还有许多其他解释器和虚拟机的变体。你可以有一个生成解析树的编译器,以及一个解释它们的解释器(但是如果它是使用递归函数实现的,那么可以认为它仍然是一个基于堆栈的虚拟机)。
使编译器不是生成某种类型的机器代码(用于VM或真实机器)来生成另一种语言的代码也并不罕见。 C是这些类型编译器的通用目标语言,因为C语言及其编译器无处不在。但是你没有VM或解释器了,你只需要一个编译器/翻译器。
答案 1 :(得分:1)
你的建议有点可能。 C实际上并没有让你操作堆栈,当你调用一个函数时,它不知道它周围的局部变量,所以你需要在堆上分配一块内存以保留一些虚假的“堆栈” space“用于脚本语言的局部变量,并将其传递给每个函数(或将其填充到线程全局)。对于脚本语言的函数调用,您还需要一个用于该堆栈的基指针。
一旦你这样做了,你已经完成了大部分工作来获得基于堆栈的语言。所以你可以做其余的事情。要使用实际的堆栈和基本指针,您必须下拉到机器语言级别。
如果你的语言是基于寄存器的,它仍然需要一个堆栈来访问局部变量(它只是不经常使用它),你只是不要将它用于指令参数那么多。如果我可以简单地简化,基于3地址寄存器的VM是基于堆栈的VM的一种特殊情况。
字节码解释器的另一种方法是让指令包含指令ID,然后将其用作函数指针数组的索引,每个函数指针都实现一条指令。
显然这样做会影响性能。如果您的指令操作非常简单,您可以通过直接在机器代码中实现它们并节省函数调用的(通常可以忽略的)开销来节省CPU周期,甚至可以使用真实堆栈而不是假堆栈。
这完全取决于您的需求。对于大多数情况,特别是如果这是你的第一个解析器/解释器/ VM,我建议使用一组函数指针和一个假堆栈。它很简单,不太难调试,而且在现代机器上足够快。您随后可以随时进入并编写以不同方式执行操作的优化版本。
E.g。一种方法是为函数调用生成足够的机器代码,然后在生成的机器代码中插入指向此类函数的指针。因此每个脚本都成为一个编译代码块,但您不必编写完整的编译器。从那以后,您可以通过将较少使用的东西留作函数来为它们生成汇编程序来改进单个的关键指令。这有点改善了代码的局部性,这是一个微小的微优化,可以提供帮助。但只是其中之一。
哦,大约一个月前,我在博客上写了如何从初学者的角度制作编译器(和字节码解释器),这可能会有所帮助:http://orangejuiceliberationfront.com/how-to-write-a-compiler/