4.从语法树到OP CODE
知道咱们的虚拟机能够执行OP CODE之后,下一步就要考虑,怎么从语法树里面生成咱们需要的OP CODE了。简单来讲,语法树就是将程序的逻辑按照树状组织并保存在内存中的一种形式。有关于更详细的信息,搜“Syntax Tree”,到处都是解释。
一时不明白也没关系,我们来看一个直观的例子。考虑a+b这样一个基本形式的表达式。这个表达式既可以按照我们所写的这样,分为a,+,b三个部分串行表示,也可以表示成下图的样子
可能一个表达式你还看不出来树形的优势。要是表达式级联起来,就显示出这种表示的威力了:
这样一个语法树,可以不借助任何别的手段,保存了表达式的优先级关系。这里的语法树表示的就是(A+B)*C的表达式。同时,在语法树上求值也很方便,后根遍历语法树就可以了。即先算出左右节点的值,再根据当前节点符号求出当前节点值。
王阳明说,知行合一。知道了语法树是什么东西,我们就要开始考虑怎么用了。“怎么用”这个问题可以分成两个部分,第一,语法树怎么实现。第二,语法树怎么生成op code。啊,先不要把语法树想象的这么复杂。在这里,我们的运算符只有加号,一个加号也只能带两个int的值节点,而不能递归的带上一个符号节点。也就是说,这棵树只可能有一种形式而已。
首先来解决语法树怎么实现的问题。在这个问题上,我们只需要把握一点,语法树是一个天然的composite模式。我们用一个UML来看看这个只有加法算符的语法树定义:
唔,很简洁,不是么。Node_type是一个syntax_node_types类型的枚举,这个枚举告诉以后的代码生成器这个抽象的node究竟是个什么类型,然后代码生成器再还原它原本的类型并生成适当的代码。op是一个operators类型的枚举,表示一个二元运算的操作符。对于本例,只有operators::add可用。
在有了基本实现之后,再考虑一下其它需求,例如语法树节点类型之间的可能存在的循环依赖问题,语法树的深浅拷贝问题,等等,最终SASL的语法树节点接口是这样的:
1 struct node{
2 syntax_node_types type;
3 template <typename NodeT> NodeT* clone() const;
4 template <typename NodeT> NodeT* deepcopy() const;
5 protected:
6 virtual node* clone_impl() const = 0;
7 virtual node* deepcopy_impl() const = 0;
8 };
9
10 struct binary_expression: public node{
11 operators op;
12 boost::shared_ptr<constant> left_expr;
13 boost::shared_ptr<constant> right_expr;
14 };
15
16 struct constant: public node{
17 int val;
18 };
道理复杂,不过实际上,并没有那么复杂吧?
下面来解决第二个问题:怎么用表达式树产生代码?我不多解释,直接上代码,相信你一定会看明白的:
1 vm_codegen& vm_codegen::emit_expression( const binary_expression& expr ){
2 if ( expr.op != operators::add ){ return *this; }
3 int c0 = expr.left_expr->val;
4 int c1 = expr.right_expr->val;
5 ins_.push_back( instruction( op_loadrc, r0, c0 ) );
6 ins_.push_back( instruction( op_loadrc, r1, c1 ) );
7 ins_.push_back( instruction( op_add, r0, r1 ) );
8 return *this;
9 }
然后我们将生成语法树,生成code,运行code的代码补上,运行,OK~
你一定会说,啊,硬性绑定寄存器!太可怕了!如果表达式复杂了该怎么办呢?呵呵。这些都是以后的问题了。以后的问题,就由以后的我们去解决好了。今日事,今日毕,时间不早,咱们还是洗洗睡了。