編譯原理-詞法分析(lexical analysis)初識

這學期開始上計算機專業最難(據說是公認的)的一門課程——編譯原理。還好,老師很有經驗並且教得很用心。用的教材是《編譯原理及實踐》,寒假時上了豆瓣看了一下評論,據說是英文原版比中譯版讀起來還要通順易懂,於是就借來英文版Compiler ConstructionPrinciple and Practice,還真的不是那麼難讀懂,給我入門剛剛好。=) 

可是,第一節課老師纔跟大家說“這本教材翻譯得有點難懂,有能力的同學儘量讀英文版。”噢,其他同學們其實早就買了中譯版,老師這才如此建議...

---------------------------------------正文---------------------------------- 

按照第二章“Scanning”其實就是“詞法分析 lexical analysis”的樣例,需要將以下程序作爲樣例輸入,進行詞法分析獲得一個個的Token及其可能的對應類別,例如保留字、特殊符號、數值、標識符等等。 

樣例輸入 sample.tny (.tny是教材中使用的稱爲Tiny語言的後綴):

 

  1. { Sample program 
  2.   in TINY language - 
  3.   computes factorial 
  4. read x; { input an integer } 
  5. if 0 < x then { don't compute if x <= 0 } 
  6.   fact := 1
  7.   repeat 
  8.     fact := fact * x; 
  9.     x := x - 1 
  10.   until x = 0
  11.   write fact  { output factorial of x } 
  12. end 

 教材中要求的輸出結果如下:

當時還沒認真學習到用有限自動機(DFA)等等概念,自己先照着教材的樣例輸出用自己的傻瓜化辦法實現一下,“追求”的目標是樣例輸出要和教材的一樣(當然不能打印每一個句子啦)。下面是我的做法。 

★ 以下是在程序實現代碼中使用Java的正則表達式功能,並對Tiny源程序代碼做了一定的前提條件所完成的 Tiny 語言,其實就是模仿教材中的最終輸出文件解析出每一個 Token。 

思路:

默認每個Token之間都以空格“ ”隔開,因此可用Java中的正則表達式將每一行依此規律拆分爲一個個Token,然後再對每一個Token進行類別匹配(也用到正則表達式),最後按類別打印輸出。以下實現但是不能夠處理多行註釋的問題。

sample1.tny

詞法分析7 2011-03-29

  樣例輸出如下:

詞法分析8 2011-03-29 

代碼實現:

  1. package lexical_analysis; 
  2.  
  3. import java.io.BufferedReader; 
  4. import java.io.FileReader; 
  5. import java.util.regex.Pattern; 
  6.  
  7. /** 
  8.  * 假設 Tiny 源程序每個 Token 都用“空格”分開,因此可以簡單地實現: 
  9.  * 每次讀取源程序的一整行,使用Java中的正則表達式以“空格”提取出來, 
  10.  * 再對比“詞彙表”以確定每一個 Token 的類別、屬性(Attribute)。 
  11.  *  
  12.  * @author liguihao 
  13.  */ 
  14. public class SimpleLexicalAnalyser { 
  15.      
  16.     // 保留字 
  17.     private String [] reservedWords = new String[] {"read""if"
  18.                                                     "then""repeat",  
  19.                                                     "until""write""end"}; 
  20.     // 數學運算符 
  21.     private String [] arithmeticSymbols = new String[] {"+""-""*""/",  
  22.                                                         "%"":=""=""<"
  23.                                                         ">""<="">="}; 
  24.     // 源程序文件輸入流 
  25.     private BufferedReader sourceFile; 
  26.     // 代碼行數 
  27.     private int lineCount = 0
  28.      
  29.      
  30.     public SimpleLexicalAnalyser(String sourceFilePath) throws Exception { 
  31.          
  32.         // 創建並加載源程序文件輸入流 
  33.         this.sourceFile = new BufferedReader(new FileReader(sourceFilePath)); 
  34.     } 
  35.      
  36.     /** 
  37.      * 逐行掃描源程序代碼 
  38.      * @throws Exception 
  39.      */ 
  40.     public void scan() throws Exception { 
  41.         String sourceLine = ""
  42.         String[] tokens; 
  43.          
  44.         while((sourceLine = this.sourceFile.readLine()) != null) { 
  45.             lineCount++; 
  46.             System.out.printf("%d: %s\n", lineCount, sourceLine); 
  47.             // 獲取每一行的Token集合 
  48.             tokens = this.getTokens(sourceLine); 
  49.              
  50.             int size = tokens.length; 
  51.             for(int i = 0; i < size; i++) { 
  52.                  
  53.                 if(isArithmeticSymbol(tokens[i])) {     // 數學運算符         
  54.                     System.out.println("    " + lineCount + ": " + tokens[i]); 
  55.                 } else if(isReservedWord(tokens[i])) {  // 保留字 
  56.                     System.out.println("    " + lineCount + ": " + "reserved word: " + tokens[i]); 
  57.                 } else if(";".equals(tokens[i])) {      // 行結束符,即分號 
  58.                     System.out.println("    " + lineCount + ": " + tokens[i]); 
  59.                 }  else if(isID(tokens[i])) {           // 自定義標識符ID 
  60.                     System.out.println("    " + lineCount + ": " + "ID, name= " + tokens[i]); 
  61.                 } else if(isNum(tokens[i])) {           // 數值NUM 
  62.                     System.out.println("    " + lineCount + ": " + "NUM, val= " + tokens[i]); 
  63.                 } else if("{".equals(tokens[i])) {      // 行註釋符,即左大括號{ 
  64.                     break;      // 直接跳過行註釋 
  65.                 } 
  66.                  
  67.                 // 源程序文件結束符 
  68.                 if("end".equals(tokens[i])) { 
  69.                     lineCount++; 
  70.                     System.out.printf("%2d: %s\n", lineCount, "EOF"); 
  71.                     break
  72.                 } 
  73.             } 
  74.         } 
  75.     } 
  76.      
  77.     /** 
  78.      * 用“空格Space”正則表達式將每一行源代碼拆分成單個Token的集合 
  79.      */ 
  80.     public String[] getTokens(String sourceLine) { 
  81.         Pattern pattern = Pattern.compile(" "); 
  82.         return pattern.split(sourceLine); 
  83.     } 
  84.      
  85.     /** 
  86.      * 判斷是否爲“保留字” 
  87.      * @param token 
  88.      * @return 
  89.      */ 
  90.     private boolean isReservedWord(String token) { 
  91.         int size = this.reservedWords.length; 
  92.         for(int i = 0; i < size; i++) { 
  93.             if(token.equals(reservedWords[i])) { 
  94.                 return true
  95.             } 
  96.         } 
  97.         return false
  98.     } 
  99.      
  100.     /** 
  101.      * 判斷是否爲“數學運算符” 
  102.      * @param token 
  103.      * @return 
  104.      */ 
  105.     private boolean isArithmeticSymbol(String token) { 
  106.         int size = this.arithmeticSymbols.length; 
  107.         for(int i = 0; i < size; i++) { 
  108.             if(token.equals(arithmeticSymbols[i])) { 
  109.                 return true
  110.             } 
  111.         } 
  112.         return false
  113.     } 
  114.      
  115.     /** 
  116.      * 判斷是否爲“數值NUM” 
  117.      * @param token 
  118.      * @return 
  119.      */ 
  120.     private boolean isNum(String token) { 
  121.         boolean flag = Pattern.matches("[a-zA-Z]+?", token); 
  122.         return flag; 
  123.     } 
  124.      
  125.     /** 
  126.      * 判斷是否爲“ID” 
  127.      * @param token 
  128.      * @return 
  129.      */ 
  130.     private boolean isID(String token) { 
  131.         boolean flag = Pattern.matches("\\d+?", token); 
  132.         return flag; 
  133.     } 
  134.      
  135.     /** 
  136.      * “詞法分析程序”的啓動入口 
  137.      * @param args 
  138.      */ 
  139.     public static void main(String[] args) throws Exception { 
  140.          
  141.         String sourceFilePath = "sample1.tny"
  142.         SimpleLexicalAnalyser lexicalAnalyser = new SimpleLexicalAnalyser(sourceFilePath); 
  143.         lexicalAnalyser.scan(); 
  144.     } 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章