minsheng
2016-02-01 06:22:46 +08:00
可以把解释器分成两种,一种是基于语法树的解释器,一种是基于字节码的解释器。
举个例子, 3+4*5 ,基于语法树的解释器大概会是这样的:
data Expr = Lit Integer | Add Expr Expr | Mul Expr Expr
-- 构造语法树
ast :: Expr
ast = Add (Lit 3) (Mul (Lit 4) (Lit 5))
-- 解释器,使用模式匹配
eval :: Expr -> Int
eval (Lit x) = x
eval (Add e1 e2) = eval e1 + eval e2
eval (Mul e1 e2) = eval e1 * eval e2
而基于字节码的则会先把源代码翻译成一段字节码:
% 0 = 3
%1 = 4
%2 = 5
%3 = mul i32 %1, %2
%4 = add i32 %0, %3
上述代码为 LLVM 中间表示,每个 %x 代表一个虚拟寄存器,有无数个虚拟寄存器。解释器会先把这段代码翻译成只使用一定数量的寄存器的形式,比如说:
%0 = 4
%1 = 5
%0 = mul i32 %0 %1
%1 = 3
%0 = add i32 %01 %1
这里只用了两个寄存器。接着,就可以解释执行这段代码。
另一种做法就是基于栈的解释器,大概长这样:
push 3
push 4
push 5
mul ;此时栈顶是 4 与 5
add ;此时栈顶是 3 与 20
据说这种方法实现起来比较简单,但是没有基于寄存器的解释方法来得快。如果没有记错的话, Lua 就是基于寄存器的解释,而 JVM 则很长一段时间都基于栈。
所谓的编译器,无非就是只完成了到字节码的翻译步骤,将执行交给硬件完成。不过编译器这个概念依然没有意义,因为硬件也是可以模拟的,比如说 Bochs ,比如说 QEMU 。难不成我们把 GCC 编译出来的代码换个环境之行,它就变成了解释器了?同理, JVM 也可以用硬件实现。我认为,只要记得基于语法树的解释和基于字节码的解释这一区别即可。