手写编译器
谈谈尾递归在变编译器中的实现
这一篇主要讲述生产式到java代码的生成过程;
何为生产式
expr -> expr + term {print('+')}
| expr - term {print('-')}
| term
term -> 0 {print('0')}
|1 {print('1')}
|2 {print('2')}
...
|9 {print('9')}
expr : 表示no ternimal (非终结符号,可以继续解析)
term : 表示no ternimal (非终结符号,可以继续解析)
0,1 … 9 : 数字(terminal 终结符号)
+ - : 操作符(terminal 终结符号)
| : 或者的意思,表示当前非终结符号可以解析的可能性
就expr 解析来说 有3种解析路劲,但是有的解析路径有可以又包含expr , 又可以继续解析,这是一个递归过程;
关于生产式的大致讲到这,总体概念是这样,想要更多细节还得深入。
生产式的解析
生产式的解析需要借助抽象语法树(Abstract and concrete tree)。比如9-5+2是符合上述生产式的,其抽象语法树
+
/ \
- 2
/ \
9 5
我们知道上述的生生产式解析其实有左递归问题的(LR),可以就看成是 A代表expr。则 A->Aa|Aß|y 就是一个左递归产常见的例子,和上面的生产式类似;
消除该左递归之后可得到(消除方法见另一篇手写编译器-消除左递归)
expr -> term rest
rest -> + term {print('-')} rest
| - term {print('-')} rest
| £
term -> 0 {print('0')}
|1 {print('1')}
|2 {print('2')}
...
|9 {print('9')}
该解析树用代码java伪代码实现
void expr(){
term();reset();
}
void rest(){
if(lookahead == '+'){
match('+');term();print('+');rest();
}else if(lookahead == '-'){
match('-');term();print('-');rest();
}else{
//do nothing
}
}
void term(){
if (lookahead is a digit){
t=lookahead;match(lookahead);print(t)
}
else{
report("Syntax error);
}
}
上述代码是正常将生产式转换而成的。但是采用带了递归的方法;
- 递归比较复杂,不够简化
- 递归会当数据量比较大的时候,会加剧深栈操作,我们显示写代码中应该尽量避免
深栈操作的避免也体现在Spring reactor对链路的优化,相反Rxjava采用了深栈操作(后续再给源码大家看) - 编译器避免了递归而导致的深栈操作,尾递归
故可进一步简化上述代码的rest方法,用循环替换,亦可以达到上述方法的实现
void rest(){
while(true){
if(lookahead == '+'){
match('+');term();print('+');continue;
}else if(lookahead == '-'){
match('-');term();print('-');continue;
}
break;
}
}
下面将完整的代码实现贴上,为大家理解javac的源码助一臂之力。
import java.io.*;
class Parser{
static int lookahead;//输入位置,当前游标
public Parser(){
lookahead = System.in.read();
}
void expr() throw IoException{
term();
while(true){
if(lookahead == '+'){
match('+');term();System.out.print('+');continue;
}else if(lookahead == '-'){
match('-');term();System.out.print('-');continue;
}
break;
}
}
void term() throw IoException {
if(Character.isDigit((char)lookahead)){
System.out.print(lookahead);match(lookahead);
}
throw new IoException("Syntax error");
}
void match(int t){
if(lookahead == t){
lookahead =System.in.read();
}
throw new IoException("Syntax error");
}
public class PostFix{
public static void main(String[] args){
Parser parser = new Parser();
parser.expr();
System.out.print("\n");
}
}
}