這學期開始上計算機專業最難(聽說是公認的)的一門課程——編譯原理。還好,老師頗有經驗而且教得很用心。用的教材是《編譯原理及實踐》,寒假時上了豆瓣看了一下評論,聽說是英文原版比中譯版讀起來還要通順易懂,因而就借來英文版《Compiler Construction:Principle and Practice》,還真的不是那麼難讀懂,給我入門剛恰好。=) java
但是,第一節課老師纔跟你們說「這本教材翻譯得有點難懂,有能力的同窗儘可能讀英文版。」噢,其餘同窗們其實早就買了中譯版,老師這才如此建議...
---------------------------------------正文---------------------------------- ide
按照第二章「Scanning」其實就是「詞法分析 lexical analysis」的樣例,須要將如下程序做爲樣例輸入,進行詞法分析得到一個個的Token及其可能的對應類別,例如保留字、特殊符號、數值、標識符等等。 學習
樣例輸入 sample.tny (.tny是教材中使用的稱爲Tiny語言的後綴):ui
- { Sample program
- in TINY language -
- computes factorial
- }
- read x; { input an integer }
- if 0 < x then { don't compute if x <= 0 }
- fact := 1;
- repeat
- fact := fact * x;
- x := x - 1
- until x = 0;
- write fact { output factorial of x }
- end
教材中要求的輸出結果以下:this
當時還沒認真學習到用有限自動機(DFA)等等概念,本身先照着教材的樣例輸出用本身的傻瓜化辦法實現一下,「追求」的目標是樣例輸出要和教材的同樣(固然不能打印每個句子啦)。下面是個人作法。 spa
★ 如下是在程序實現代碼中使用Java的正則表達式功能,並對Tiny源程序代碼作了必定的前提條件所完成的 Tiny 語言,其實就是模仿教材中的最終輸出文件解析出每個 Token。 翻譯
思路:
默認每一個Token之間都以空格「 」隔開,所以可用Java中的正則表達式將每一行依此規律拆分爲一個個Token,而後再對每個Token進行類別匹配(也用到正則表達式),最後按類別打印輸出。如下實現可是不可以處理多行註釋的問題。blog
sample1.tny
樣例輸出以下:
代碼實現:
- package lexical_analysis;
- import java.io.BufferedReader;
- import java.io.FileReader;
- import java.util.regex.Pattern;
- /**
- * 假設 Tiny 源程序每一個 Token 都用「空格」分開,所以能夠簡單地實現:
- * 每次讀取源程序的一整行,使用Java中的正則表達式以「空格」提取出來,
- * 再對比「詞彙表」以肯定每個 Token 的類別、屬性(Attribute)。
- *
- * @author liguihao
- */
- public class SimpleLexicalAnalyser {
- // 保留字
- private String [] reservedWords = new String[] {"read", "if",
- "then", "repeat",
- "until", "write", "end"};
- // 數學運算符
- private String [] arithmeticSymbols = new String[] {"+", "-", "*", "/",
- "%", ":=", "=", "<",
- ">", "<=", ">="};
- // 源程序文件輸入流
- private BufferedReader sourceFile;
- // 代碼行數
- private int lineCount = 0;
- public SimpleLexicalAnalyser(String sourceFilePath) throws Exception {
- // 建立並加載源程序文件輸入流
- this.sourceFile = new BufferedReader(new FileReader(sourceFilePath));
- }
- /**
- * 逐行掃描源程序代碼
- * @throws Exception
- */
- public void scan() throws Exception {
- String sourceLine = "";
- String[] tokens;
- while((sourceLine = this.sourceFile.readLine()) != null) {
- lineCount++;
- System.out.printf("%d: %s\n", lineCount, sourceLine);
- // 獲取每一行的Token集合
- tokens = this.getTokens(sourceLine);
- int size = tokens.length;
- for(int i = 0; i < size; i++) {
- if(isArithmeticSymbol(tokens[i])) { // 數學運算符
- System.out.println(" " + lineCount + ": " + tokens[i]);
- } else if(isReservedWord(tokens[i])) { // 保留字
- System.out.println(" " + lineCount + ": " + "reserved word: " + tokens[i]);
- } else if(";".equals(tokens[i])) { // 行結束符,即分號
- System.out.println(" " + lineCount + ": " + tokens[i]);
- } else if(isID(tokens[i])) { // 自定義標識符ID
- System.out.println(" " + lineCount + ": " + "ID, name= " + tokens[i]);
- } else if(isNum(tokens[i])) { // 數值NUM
- System.out.println(" " + lineCount + ": " + "NUM, val= " + tokens[i]);
- } else if("{".equals(tokens[i])) { // 行註釋符,即左大括號{
- break; // 直接跳過行註釋
- }
- // 源程序文件結束符
- if("end".equals(tokens[i])) {
- lineCount++;
- System.out.printf("%2d: %s\n", lineCount, "EOF");
- break;
- }
- }
- }
- }
- /**
- * 用「空格Space」正則表達式將每一行源代碼拆分紅單個Token的集合
- */
- public String[] getTokens(String sourceLine) {
- Pattern pattern = Pattern.compile(" ");
- return pattern.split(sourceLine);
- }
- /**
- * 判斷是否爲「保留字」
- * @param token
- * @return
- */
- private boolean isReservedWord(String token) {
- int size = this.reservedWords.length;
- for(int i = 0; i < size; i++) {
- if(token.equals(reservedWords[i])) {
- return true;
- }
- }
- return false;
- }
- /**
- * 判斷是否爲「數學運算符」
- * @param token
- * @return
- */
- private boolean isArithmeticSymbol(String token) {
- int size = this.arithmeticSymbols.length;
- for(int i = 0; i < size; i++) {
- if(token.equals(arithmeticSymbols[i])) {
- return true;
- }
- }
- return false;
- }
- /**
- * 判斷是否爲「數值NUM」
- * @param token
- * @return
- */
- private boolean isNum(String token) {
- boolean flag = Pattern.matches("[a-zA-Z]+?", token);
- return flag;
- }
- /**
- * 判斷是否爲「ID」
- * @param token
- * @return
- */
- private boolean isID(String token) {
- boolean flag = Pattern.matches("\\d+?", token);
- return flag;
- }
- /**
- * 「詞法分析程序」的啓動入口
- * @param args
- */
- public static void main(String[] args) throws Exception {
- String sourceFilePath = "sample1.tny";
- SimpleLexicalAnalyser lexicalAnalyser = new SimpleLexicalAnalyser(sourceFilePath);
- lexicalAnalyser.scan();
- }
- }