用java實現一個簡易編譯器1-詞法解析入門


本文對應代碼下載地址為:

http://download.csdn.net/detail/tyler_download/9435103

視頻地址:

http://v.youku.com/v_show/id_XMTQ3NTQwMDkxMg==.html?from=s1.8-1-1.2

 

技術的發展可謂是日新月異,層出不窮,但無論是炙手可熱的大數據,還是火燒鳥了的人工智能,所有這些高大上的尖端科技無不建立在基礎技術的根基之上。編譯原理,計算機網絡,操作系統,便是所有軟件技術的基石。在這三根支柱中,維編譯原理最為難懂,特別是大學課本那種晦澀難通,不講人話的言語,更是讓人覺得這門基礎技術就像九十多歲的老嫗,皮膚干巴,老態龍鍾,讓人提不起一點欲望。除了國內教材,就算是被廣為稱贊的一千多頁的”龍書“,也是滿篇理論,讓人望而生畏。

味道怎樣,咬一口就知道,手感如何,摸一把就曉得。編譯原理缺的不是理論概念,而是能夠動手實踐的流程,代碼,很多原理用話語怎么講都難以明了,但跑一遍代碼,基本就水落石出。本文本着動手實操(念第一聲)的原則,用java實現一個簡單的編譯器,讓讀者朋友能一感編譯原理的實質,我秉持一個原則,沒有代碼可實踐的計算機理論,都是耍流氓。

 

編譯器作用就是將一種計算機無法理解的文本,轉譯成計算機能執行的語句,我們要做的編譯器如下,將帶有加法和乘法的算術式子,轉譯成機器能執行的匯編語句,例如語句:

1+2*3+4, 經過編譯后轉換成:

t0 = 1

t1 = 2

t2 = 3

t1 *= t2

t0 += t1

t1 = 4

t0 += t1

t0, t1 是對寄存器的模擬,上述語句基本上就類似計算機能執行的匯編語句了。

 

本章首先專注於詞法解析的探討。

 

編譯原理由兩部分組成,一是詞法分析,一是語義分析。先說詞法分析,詞法分析就是將一個語句分割成若干個有意義的字符串的組合,然后給分割的字符串打標簽。例如語句:

1+2*3+4; 可以分割成 1+, 2*, 3+, 4; 但這些子字符串沒有實質意義,有意義的分割是1, +, 2, * , 3, +, 4, ;. 接着就是給這些分割后的字符串打標簽,例如給1, 2, 3, 4 打上的標簽是NUM_OR_ID,  + 打的標簽是PLUS, *的標簽是TIMES, ;的標簽是SEMI, 好了,看看詞法分析的代碼,大家可能更容易理解:

Lexer.java:

 

[java]  view plain  copy
 
  1. import java.util.Scanner;  
  2.   
  3.   
  4. public class Lexer {  
  5.     public static final int  EOI = 0;  
  6.     public static final int  SEMI = 1;  
  7.     public static final int  PLUS = 2;  
  8.     public static final int  TIMES = 3;  
  9.     public static final int  LP = 4;  
  10.     public static final int  RP = 5;  
  11.     public static final int  NUM_OR_ID = 6;  
  12.       
  13.     private int lookAhead = -1;  
  14.       
  15.     public String yytext = "";  
  16.     public int yyleng = 0;  
  17.     public int yylineno = 0;  
  18.       
  19.     private String input_buffer = "";  
  20.     private String current = "";  
  21.       
  22.     private boolean isAlnum(char c) {  
  23.         if (Character.isAlphabetic(c) == true ||  
  24.                 Character.isDigit(c) == true) {  
  25.             return true;  
  26.         }  
  27.           
  28.         return false;  
  29.     }  
  30.       
  31.     private int lex() {  
  32.       
  33.         while (true) {  
  34.               
  35.             while (current == "") {  
  36.                 Scanner s = new Scanner(System.in);  
  37.                 while (true) {  
  38.                     String line = s.nextLine();  
  39.                     if (line.equals("end")) {  
  40.                         break;  
  41.                     }  
  42.                     input_buffer += line;  
  43.                 }  
  44.                 s.close();  
  45.                   
  46.                 if (input_buffer.length() == 0) {  
  47.                     current = "";  
  48.                     return EOI;  
  49.                 }  
  50.                   
  51.                 current = input_buffer;  
  52.                 ++yylineno;  
  53.                 current.trim();  
  54.             }//while (current != "")  
  55.                   
  56.                 for (int i = 0; i < current.length(); i++) {  
  57.                       
  58.                     yyleng = 0;  
  59.                     yytext = current.substring(0, 1);  
  60.                     switch (current.charAt(i)) {  
  61.                     case ';': current = current.substring(1); return SEMI;  
  62.                     case '+': current = current.substring(1); return PLUS;  
  63.                     case '*': current = current.substring(1);return TIMES;  
  64.                     case '(': current = current.substring(1);return LP;  
  65.                     case ')': current = current.substring(1);return RP;  
  66.                       
  67.                     case '\n':  
  68.                     case '\t':  
  69.                     case ' ': current = current.substring(1); break;  
  70.                       
  71.                     default:  
  72.                         if (isAlnum(current.charAt(i)) == false) {  
  73.                             System.out.println("Ignoring illegal input: " + current.charAt(i));  
  74.                         }  
  75.                         else {  
  76.                               
  77.                             while (isAlnum(current.charAt(i))) {  
  78.                                 i++;  
  79.                                 yyleng++;  
  80.                             } // while (isAlnum(current.charAt(i)))  
  81.                               
  82.                             yytext = current.substring(0, yyleng);  
  83.                             current = current.substring(yyleng);   
  84.                             return NUM_OR_ID;  
  85.                         }  
  86.                           
  87.                         break;  
  88.                           
  89.                     } //switch (current.charAt(i))  
  90.                 }//  for (int i = 0; i < current.length(); i++)   
  91.               
  92.         }//while (true)   
  93.     }//lex()  
  94.       
  95.     public boolean match(int token) {  
  96.         if (lookAhead == -1) {  
  97.             lookAhead = lex();  
  98.         }  
  99.           
  100.         return token == lookAhead;  
  101.     }  
  102.       
  103.     public void advance() {  
  104.         lookAhead = lex();  
  105.     }  
  106.       
  107.     public void runLexer() {  
  108.         while (!match(EOI)) {  
  109.             System.out.println("Token: " + token() + " ,Symbol: " + yytext );  
  110.             advance();  
  111.         }  
  112.     }  
  113.       
  114.     private String token() {  
  115.         String token = "";  
  116.         switch (lookAhead) {  
  117.         case EOI:  
  118.             token = "EOI";  
  119.             break;  
  120.         case PLUS:  
  121.             token = "PLUS";  
  122.             break;  
  123.         case TIMES:  
  124.             token = "TIMES";  
  125.             break;  
  126.         case NUM_OR_ID:  
  127.             token = "NUM_OR_ID";  
  128.             break;  
  129.         case SEMI:  
  130.             token = "SEMI";  
  131.             break;  
  132.         case LP:  
  133.             token = "LP";  
  134.             break;  
  135.         case RP:  
  136.             token = "RP";  
  137.             break;  
  138.         }  
  139.           
  140.         return token;  
  141.     }  
  142. }  



 

代碼中2到6行是對標簽的定義,其中LP 代表左括號(,  RP代表右括號), EOI 表示語句末尾, 第10行的lookAhead 變量用於表明當前分割的字符串指向的標簽值,yytext用於存儲當前正在分析的字符串,yyleng是當前分析的字符串的長度,yylineno是當前分析的字符串所在的行號。input_buffer 用於存儲要分析的語句例如: 1+2*3+4;  isAlNum 用於判斷輸入的字符是否是數字或字母。lex() 函數開始了詞法分析的流程,31到40行從控制台讀入語句,語句以"end"表明結束,例如在控制台輸入:

1+2*3+4;

end

回車后,從52行開始執行詞法解析流程。以上面的輸入為例,input_buffer 存儲語句 1+2*3+4, 由於第一個字符是 1, 在for 循環中,落入switch 的default 部分,isAlNum 返回為真,yyleng 自加后值為1, yytext 存儲的字符串就是 "1", current前進一個字符變為+2*3+4, 再次執行lex(), 則解析的字符是+, 在for 循環中,落入switch的case '+' 分支,於是yytext為"+", 返回的標簽就是PLUS依次類推, advance 調用一次, lex()就執行一次詞法分析,當lex執行若干次后,語句1+2*3+4;會被分解成1, +, 2, *, 3, +, 4, ; 。字符串1, 2, 3, 4具有的標簽是NUM_OR_ID, + 具有的標簽是PLUS, *的標簽是TIMES, ;的標簽是SEMI.

 

runLexer() 將驅動詞法解析器,執行解析流程,如果解析到的當前字符串,其標簽不是EOI(end of input), 也就是沒有達到輸入末尾,那么就打印出當前分割的字符串和它所屬的標簽,接着調用advance() 進行下一次解析。

 

match, advance 會被稍后我們將看到的語法解析器調用。

 

接下來我們在main函數中,跑起Lexer, 看看詞法解析過程:

Compiler.java

 

[java]  view plain  copy
 
  1. public class Compiler {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Lexer lexer = new Lexer();  
  5.         //Parser parser = new Parser(lexer);  
  6.         //parser.statements();  
  7.         lexer.runLexer();  
  8.     }  
  9. }  


在eclipse 中運行給定代碼,然后在控制台中輸入如下:

 

 

1+2*3+4;

end

程序運行后輸出:

 

 

Token: NUM_OR_ID ,Symbol: 1

Token: PLUS ,Symbol: +

Token: NUM_OR_ID ,Symbol: 2

Token: TIMES ,Symbol: *

Token: NUM_OR_ID ,Symbol: 3

Token: PLUS ,Symbol: +

Token: NUM_OR_ID ,Symbol: 4

Token: SEMI ,Symbol: ;

 

后記:

該篇敘述的只是一個簡單的詞法解析入門,希望通過可運行的代碼,讓大家能體會一下詞法分析的流程,從感性上獲得直接的認識,為后續理解完整專業的詞法解析打下基礎。

完整的代碼我會上傳到csdn, 大家可以獲得代碼后,自己運行嘗試一下。我將在后續的文章中,繼續與大家一起探討一個完整編譯器的開發。

 

另外,我希望將此教程制作成視頻模式,大家通過觀看視頻,可以更直觀的看到代碼調試,解析,運行等流程,更容易學習和加深理解,如果哪位朋友有興趣,留個郵箱,我把

制作好的視頻發給你們,並虛心的向諸位朋友求教。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM