这个项目主要是为了考察考察对解释器、以及CPS/SSA的理解。
定义这个微型的字节码指令集是小case,暂不考虑,cps转换算法也不需要考虑(可能是隐式的,而非lisp/scheme里面的那种源代码级别的转换)
例如,表达式 1+sin(2*3),对应的指令序列可能如下:
LoadImm 1, R0 StoreLocal R0, %0 #将number 1存储到局部变量槽%0里面 LoadImm 2, R0 #加载立即数到寄存器 LoadImm 3, R1 CallPrimitiveFunction sin #临时变量的结果现在在R0里 Move R0, R1 #将结果从R0移动到R1 LoadLocal %0, R0 CallPrimitiveFunction ADD
从这个简单的手工转换的例子来讲,我发现了一个问题:
考虑一个二元函数,就是这里的+,它的左边是一个子表达式Left,右边Right,根据优先级,Right端必须先运算。则有2种处理思路:
1、先生成Right端对应的字节码,再考虑左边的——这时相当于利用+的交换律做了转换
2、先把Left端的子表达式压栈(栈目前只支持number类型的局部变量),再生成Right端对应的字节码
递归下降的parser前端架构支持2比较方便,问题是,考虑嵌套的表达式:
1+(2+(3+(4+5)))
这种情况下LoadLocal、StoreLocal指令似乎存在问题,需要在parser中管理局部变量的索引状态,对于表达式而言,似乎Push/Pop指令更直接一点
但单有Push/Pop解决不了下面的表达式:
sin(1+2)* cos(3-4)
SICP那本书的后期实现了编译器,将玩具语言编译为CPU风格的指令(其实这里没必要搞得这么复杂),然后又用Scheme去解释执行它。这就有点类似于我现在做的,只不过我的目标是在理解解释器、CPS转换的基础上,将实际的代码尽量简化。
注意,对表达式求值而言,在parser递归下降的过程中直接求值,这其实就是一个解释器了,而如果把计算器表达式转换为等价的JS代码,这其实就是编译器了,只不过是源代码级别的转换,跟真正的编译器差那么一点点。
缺点:我之前写的代码并没有实现parser解析为中间的AST,AST实际上直接去除了运算符优先级和结合性歧义的问题。
假如我想实现一个JavaScript-in-JavaScript解释器,AST生成可以借助Esprima库,但是解释器本身的runtime怎么设计就有点难度了