(2) 用java實現一個簡易編譯器1-詞法解析入門
轉載地址 : http://blog.csdn.net/tyler_download/article/details/50668983/
視訊地址 : http://study.163.com/course/courseLearn.htm?courseId=1002830012#/learn/video?lessonId=1003210101&courseId=1002830012
正文內容
本文對應程式碼下載地址為:
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:
import java.util.Scanner;
public class Lexer {
public static final int EOI = 0;
public static final int SEMI = 1;
public static final int PLUS = 2;
public static final int TIMES = 3;
public static final int LP = 4;
public static final int RP = 5;
public static final int NUM_OR_ID = 6;
private int lookAhead = -1;
public String yytext = "";
public int yyleng = 0;
public int yylineno = 0;
private String input_buffer = "";
private String current = "";
private boolean isAlnum(char c) {
if (Character.isAlphabetic(c) == true ||
Character.isDigit(c) == true) {
return true;
}
return false;
}
private int lex() {
while (true) {
while (current == "") {
Scanner s = new Scanner(System.in);
while (true) {
String line = s.nextLine();
if (line.equals("end")) {
break;
}
input_buffer += line;
}
s.close();
if (input_buffer.length() == 0) {
current = "";
return EOI;
}
current = input_buffer;
++yylineno;
current.trim();
}//while (current != "")
for (int i = 0; i < current.length(); i++) {
yyleng = 0;
yytext = current.substring(0, 1);
switch (current.charAt(i)) {
case ';': current = current.substring(1); return SEMI;
case '+': current = current.substring(1); return PLUS;
case '*': current = current.substring(1);return TIMES;
case '(': current = current.substring(1);return LP;
case ')': current = current.substring(1);return RP;
case '\n':
case '\t':
case ' ': current = current.substring(1); break;
default:
if (isAlnum(current.charAt(i)) == false) {
System.out.println("Ignoring illegal input: " + current.charAt(i));
}
else {
while (isAlnum(current.charAt(i))) {
i++;
yyleng++;
} // while (isAlnum(current.charAt(i)))
yytext = current.substring(0, yyleng);
current = current.substring(yyleng);
return NUM_OR_ID;
}
break;
} //switch (current.charAt(i))
}// for (int i = 0; i < current.length(); i++)
}//while (true)
}//lex()
public boolean match(int token) {
if (lookAhead == -1) {
lookAhead = lex();
}
return token == lookAhead;
}
public void advance() {
lookAhead = lex();
}
public void runLexer() {
while (!match(EOI)) {
System.out.println("Token: " + token() + " ,Symbol: " + yytext );
advance();
}
}
private String token() {
String token = "";
switch (lookAhead) {
case EOI:
token = "EOI";
break;
case PLUS:
token = "PLUS";
break;
case TIMES:
token = "TIMES";
break;
case NUM_OR_ID:
token = "NUM_OR_ID";
break;
case SEMI:
token = "SEMI";
break;
case LP:
token = "LP";
break;
case RP:
token = "RP";
break;
}
return token;
}
}
程式碼中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
public class Compiler {
public static void main(String[] args) {
Lexer lexer = new Lexer();
//Parser parser = new Parser(lexer);
//parser.statements();
lexer.runLexer();
}
}
在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, 大家可以獲得程式碼後,自己執行嘗試一下。我將在後續的文章中,繼續與大家一起探討一個完整編譯器的開發。
另外,我希望將此教程製作成視訊模式,大家通過觀看視訊,可以更直觀的看到程式碼除錯,解析,執行等流程,更容易學習和加深理解,如果哪位朋友有興趣,留個郵箱,我把
製作好的視訊發給你們,並虛心的向諸位朋友求教。