手寫編譯器
談談尾遞歸在變編譯器中的實現
這一篇主要講述生產式到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");
}
}
}