继续完成前面一篇“设计有穷自动机DFA实现C++简单程序的词法分析、扫描(编译原理实验)”词法分析扫猫程序剩下来关于去除多余空行、空格、注释进行源程序压缩的功能。

按实验要求(如下),这里需要考虑下面带星号*的第(3)(5)点:

 

实验中用到的C++源程序如下图:

 

思路:


其实也就是将源程序中的多余空格、注释、换行等都删除,整理成单单一行的源代码。
每次对扫描程序获取到的Token进行判断,根据上一个Token的类型(有关键字、标识符、数值、字符串、特殊符号)决定当前Token是否能够与上一个Token紧邻,也即不加任何空格。
例如上面截图中倒数第二行中的 else 和 cout 两个关键字之间就必须有空格分开,否则代码就会出错了。针对上面这个简单的C++源程序,观察其DFA图可以得出以下特点:
1、关键字与标识符不能紧邻,例如 int i中间必须有空格
2、关键字与关键字也不能紧邻,如上所述
3、另外关键字与字符串也不要紧邻


对于以上样例输入,先进行词法分析,然后将获得的Token压缩并保存在StringBuilder对象中,在写入到一个新的文件,最终再次对压缩后的文件进行扫描,判断压缩前后的扫描结果是否一直。

程序输出结果(包括压缩后的源代码)如下:

 

根据上面这三个特点,代码实现如下(高亮部分是与上一篇源代码不同之处):

  1. package lexical_analysis; 
  2.  
  3. import java.io.BufferedReader; 
  4. import java.io.File; 
  5. import java.io.FileOutputStream; 
  6. import java.io.FileReader; 
  7. import java.io.PrintWriter; 
  8.  
  9. public class Scanner_2 { 
  10.  
  11.     // 定义DFA中的所有状态表 
  12. //  enum StateType {Start, Num, ID, EQ, NE, NM, NL,  
  13. //      Com, LineCom, MulCom1, MulCom2, Special, Done, Str}; 
  14.          
  15.     // 定义DFA中的所有状态表 
  16.     private static final int Start = 1
  17.     private static final int Num = 2
  18.     private static final int ID = 3
  19.     private static final int EQ = 4
  20.     private static final int NE = 5
  21.     private static final int NM = 6
  22.     private static final int NL = 7
  23.     private static final int Coms = 8
  24.     private static final int LineCom = 9;  
  25.     private static final int MulCom1 = 10
  26.     private static final int MulCom2 = 11
  27.     private static final int Special = 12
  28.     private static final int Done = 13
  29.     private static final int Str = 14
  30.      
  31.     // Token类型,Initial为初始类型 
  32.     private enum TokenType { 
  33.         Initial, ID, Special, Str, KeyWord 
  34.     }; 
  35.          
  36.     // 关键字 
  37.     private String[] keyWords = new String[] { 
  38.         "include""define""iostream""int""folat""double"
  39.         "main""if""else""for""while""do""goto""switch"
  40.         "case""static""cin""cout" 
  41.     }; 
  42.      
  43.     // 特殊字符 
  44.     private String [] special = {"{""}""[""]""("")"
  45.              "#"",""."";"":""\\"
  46.              "'""\""">>""<<""!=""="
  47.              "==""<="">=""++""--"}; 
  48.      
  49.     // 算术运算符 
  50.     private String [] arithmetic = {"+""-""-""/""%"}; 
  51.      
  52.     // 源代码文件输入流 
  53.     private BufferedReader sourceFile; 
  54.      
  55.     // 压缩后的文件输出流 
  56.     private PrintWriter compressedFileWriter; 
  57.     // 上一个Token的类型 
  58.     private TokenType preType = TokenType.Initial; 
  59.     // 缓存去除多余空格、注释后的源代码 
  60.     private StringBuilder compressedStr = new StringBuilder(); 
  61.      
  62.     // 扫描行的最大字符数 
  63.     private static final int BUF_SIZE = 256
  64.     // 当前行的字符长度 
  65.     private int bufSize = 0
  66.     // 当前行 
  67.     private String eachLine; 
  68.     // 当前扫描行的字符序列 
  69.     private char [] lineBuf = new char[BUF_SIZE]; 
  70.     // 当前扫描的行数 
  71.     private int lineNum = 0
  72.     // 当前行的字符下标 
  73.     private int charPos = 0
  74.     // 是否已达文件尾 
  75.     private boolean isEOF = false
  76.  
  77.     /** 
  78.      * 每次扫描前都要初始化一些必要变量值 
  79.      */ 
  80.     private void initial(){ 
  81.         bufSize = 0
  82.         lineNum = 0
  83.         charPos = 0
  84.         isEOF = false
  85.     } 
  86.      
  87.     /** 
  88.      * 初始化并读取源代码文件 
  89.      * 扫描程序开始执行,直到读取文件结束符EOF 
  90.      * @throws Exception 
  91.      */ 
  92.     private void scanning(String originalFile) throws Exception { 
  93.         this.sourceFile = new BufferedReader(new FileReader(originalFile)); 
  94.          
  95.         this.initial(); 
  96.         while(!isEOF) { 
  97.             getToken(); 
  98.         } 
  99.         System.out.println("========================> end scanning ..."); 
  100.     } 
  101.      
  102.     /** 
  103.      * 获取下一个字符 
  104.      * @return 
  105.      * @throws Exception 
  106.      */ 
  107.     private char getNextChar() throws Exception { 
  108.         char nextChar = '\0'
  109.          
  110.         if(!(charPos < bufSize)) { 
  111.             if((eachLine = sourceFile.readLine()) != null) { 
  112.                 lineNum++; 
  113.                 System.out.println(lineNum + ": " + eachLine); 
  114.                 lineBuf = eachLine.toCharArray(); 
  115.                 bufSize = eachLine.length(); 
  116.                 charPos = 0
  117.                 nextChar = lineBuf[charPos++]; 
  118.             } else { 
  119.                 isEOF = true
  120.                 nextChar = '\0'
  121.             } 
  122.         } else { 
  123.             nextChar = lineBuf[charPos++]; 
  124.         } 
  125.         return nextChar; 
  126.     } 
  127.      
  128.     /** 
  129.      * 【按步长(step)】取消获取下一个字符 
  130.      */ 
  131.     private void unGetNextChar(int step) { 
  132.         if(!isEOF) { 
  133.             charPos -= step; 
  134.         } 
  135.     } 
  136.      
  137.     /** 
  138.      * 获取一个Token 
  139.      * @return 
  140.      * @throws Exception 
  141.      */ 
  142.     private String getToken() throws Exception { 
  143.         String tokenStr = ""
  144.         String currentToken = ""
  145.         int currentState = Start; 
  146.         boolean isSave; 
  147.          
  148.         // 不同时为EOF和Done状态 
  149.         while(currentState != Done && !isEOF) { 
  150.             char c = getNextChar(); 
  151.             isSave = true
  152.              
  153.             switch(currentState) { 
  154.                 case Start: 
  155.                     if(isDigit(c)) { 
  156.                         currentState = Num; 
  157.                     } else if(isLetter(c) || c == '.') { //点号是为了处理头文件iostream.h的格式 
  158.                         currentState = ID; 
  159.                     } else if(c == ' ' || c == '\t' || c == '\n') { 
  160.                         isSave = false
  161.                     } else if(c == '!') { 
  162.                         currentState = NE; 
  163.                     } else if(c == '=') { 
  164.                         currentState = EQ; 
  165.                     } else if(c == '<') { 
  166.                         currentState = NM; 
  167.                     } else if(c == '>') { 
  168.                         currentState = NL; 
  169.                     } else if(c == '/') { 
  170.                         currentState = Coms; 
  171.                         isSave = false
  172.                     } else if(c == '"') { 
  173.                         currentState = Str; 
  174.                     } else { 
  175.                         currentState = Done; 
  176. //                      if(isSingle(c)) { 
  177. //                          currentToken = "" + c; 
  178. //                          currentState = Done; 
  179. //                          isSave = false; 
  180. //                      } 
  181.                     } 
  182.                     break
  183.                 case Num: 
  184.                     if(!isDigit(c)) { 
  185.                         currentState = Done; 
  186.                         unGetNextChar(1); 
  187.                         isSave = false
  188.                     } 
  189.                     break
  190.                 case ID: 
  191.                     if(!isLetter(c) && !isDigit(c)) { 
  192.                         currentState = Done; 
  193.                         unGetNextChar(1); 
  194.                         isSave = false
  195.                     } 
  196.                     break
  197.                 case NE: 
  198.                     if(c != '=') { 
  199.                         currentState = Special; 
  200.                         unGetNextChar(2); 
  201.                         isSave = false
  202.                     } else { 
  203.                         currentState = Done; 
  204.                     } 
  205.                     break
  206.                 case NM: 
  207.                     if(c != '=' && c != '<') { 
  208.                         currentState = Special; 
  209.                         unGetNextChar(2); 
  210.                         isSave = false
  211.                     } else { 
  212.                         currentState = Done; 
  213.                     } 
  214.                     break
  215.                 case NL: 
  216.                     if(c != '=' && c != '>') { 
  217.                         currentState = Special; 
  218.                         unGetNextChar(2); 
  219.                         isSave = false
  220.                     } else { 
  221.                         currentState = Done; 
  222.                     } 
  223.                     break
  224.                 case EQ: 
  225.                     if(c != '=') { 
  226.                         currentState = Special; 
  227.                         unGetNextChar(2); 
  228.                         isSave = false
  229.                     } else { 
  230.                         currentState = Done; 
  231.                     } 
  232.                     break
  233.                 case Str: 
  234.                     if(c == '"') { 
  235.                         currentState = Done; 
  236.                     }  
  237.                     break
  238.                 case Coms: 
  239.                     isSave = false
  240.                     if(c == '/') { 
  241.                         currentState = LineCom; 
  242.                     } else if(c == '*') { 
  243.                         currentState = MulCom1; 
  244.                     } else { 
  245.                         currentState = Special; 
  246.                         unGetNextChar(1); 
  247.                     } 
  248.                     break
  249.                 case LineCom: 
  250.                     isSave = false
  251.                     if(c == '\n') { 
  252.                         currentState = Done; 
  253.                     } 
  254.                     break
  255.                 case MulCom2: 
  256.                     isSave = false
  257.                     if(c == '*') { 
  258.                         currentState = MulCom2; 
  259.                     } else if(c == '/') { 
  260.                         currentState = Done; 
  261.                     } else { 
  262.                         currentState = MulCom1; 
  263.                     } 
  264.                     break
  265.                 case Special: 
  266.                     if(c == '!' || c == '=' || c == '<' || c == '>') { 
  267. //                  if(isSpecialSingle(c)) { 
  268.                         currentToken = "" + c; 
  269.                         currentState = Done; 
  270.                         isSave = false
  271.                     } else { 
  272.                         currentToken = "Error"
  273.                         currentState = Done; 
  274.                     } 
  275.                     break
  276.                 default
  277.                     System.out.println(lineNum + " >> Scanner Bug : state = " + currentState); 
  278.                     currentState = Done; 
  279.                     currentToken = "Error"
  280.                     break
  281.             } 
  282.             if(isSave) { 
  283.                 tokenStr += c; 
  284.             } 
  285.             if(currentState == Done) { 
  286.                 currentToken = tokenStr; 
  287.                 printToken(currentToken); 
  288.             } 
  289.         } 
  290.         return currentToken; 
  291.     } 
  292.      
  293.     /** 
  294.      * 判断是否为字母 
  295.      * @param c 
  296.      * @return 
  297.      */ 
  298.     private boolean isLetter(char c) { 
  299.         if(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')){ 
  300.             return true
  301.         } 
  302.         return false
  303.     } 
  304.      
  305.     /** 
  306.      * 判断是否为数字 
  307.      * @param c 
  308.      * @return 
  309.      */ 
  310.     private boolean isDigit(char c) { 
  311.         if('0' <= c && c <= '9') { 
  312.             return true
  313.         } 
  314.         return false
  315.     } 
  316.      
  317.     /** 
  318.      * 打印时判断是否为【数值Num】 
  319.      * @param token 
  320.      * @return 
  321.      */ 
  322.     private boolean isNum(String token) { 
  323.         boolean flag = true
  324.         char [] chs = token.toCharArray(); 
  325.         int len = chs.length; 
  326.         for(int i = 0; i < len; i++) { 
  327.             if(!isDigit(chs[i])) { 
  328.                 flag = false
  329.             } 
  330.         } 
  331.         return flag; 
  332.     } 
  333.      
  334.     /** 
  335.      * 打印时判断是否为【特殊符号】 
  336.      */ 
  337.     private boolean isSpecial(String token) { 
  338.         int len = special.length; 
  339.         for(int i = 0; i < len; i++) { 
  340.             if(token.equals(special[i])) { 
  341.                 return true
  342.             } 
  343.         } 
  344.         return false
  345.     } 
  346.      
  347.     /** 
  348.      * 判断是否为算术运算符 
  349.      * @param token 
  350.      * @return 
  351.      */ 
  352.     private boolean isArithmetic(String token) { 
  353.         int len = arithmetic.length; 
  354.         for(int i = 0; i < len; i++) { 
  355.             if(token.equals(arithmetic[i])) { 
  356.                 return true
  357.             } 
  358.         } 
  359.         return false
  360.     } 
  361.      
  362.     /** 
  363.      * 打印时判断是否为【关键字】 
  364.      * @param token 
  365.      * @return 
  366.      */ 
  367.     private boolean isKeyWord(String token) { 
  368.         int len = keyWords.length; 
  369.         for(int i = 0; i < len; i++) { 
  370.             if(keyWords[i].equals(token)) { 
  371.                 return true
  372.             } 
  373.         } 
  374.         return false
  375.     } 
  376.      
  377.     /** 
  378.      * 判断是否为【单个字符】即 # * { } [ ] ( ) , . ; : ' 
  379.      * @param c 
  380.      * @return 
  381.      */ 
  382. //  private boolean isSingle(char c) { 
  383. //      char [] single = {'#', '*', '{', '}', 
  384. //                          '[', ']', '(', ')', 
  385. //                          ':', ';', '.', ',', 
  386. //                          '\''}; 
  387. //      int len = single.length; 
  388. //      for(int i = 0; i < len; i++) { 
  389. //          if(c == single[i]) { 
  390. //              return true; 
  391. //          } 
  392. //      } 
  393. //      return false; 
  394. //  } 
  395.      
  396.     /** 
  397.      * 判断是否为【单个的特殊字符】即 !   =   <   >  
  398.      * 因为这几个属于多义字符,能形成 !=  ==  <<  >> 
  399.      * @param c 
  400.      * @return 
  401.      */ 
  402. //  private boolean isSpecialSingle(char c) { 
  403. //      char [] special = {'!', '=', '<', '>'}; 
  404. //      int len = special.length; 
  405. //      for(int i = 0; i < len; i++) { 
  406. //          if(c == special[i]) { 
  407. //              return true; 
  408. //          } 
  409. //      } 
  410. //      return false; 
  411. //  } 
  412.      
  413.     /** 
  414.      * 按类别打印扫描得到的Token 
  415.      * @param token 
  416.      */ 
  417.     private void printToken(String token) { 
  418.         if(isKeyWord(token)) { 
  419.             System.out.printf("%4d: %s --- %s\n", lineNum, token, "关键字"); 
  420.              
  421.             token = (preType == TokenType.KeyWord ? " " : "") + token; 
  422.             preType = TokenType.KeyWord;  
  423.             this.compressedStr.append(token); 
  424.              
  425.         } else if(isSpecial(token)) { 
  426.             System.out.printf("%4d: %s --- %s\n", lineNum, token,"特殊符号"); 
  427.              
  428.             preType = TokenType.Special; 
  429.             this.compressedStr.append(token); 
  430.              
  431.         } else if(isArithmetic(token)) { 
  432.             System.out.printf("%4d: %s --- %s\n", lineNum, token,"算术运算符"); 
  433.              
  434.             preType = TokenType.Special; 
  435.             this.compressedStr.append(token); 
  436.              
  437.         } else if(isNum(token)) { 
  438.             System.out.printf("%4d: %s --- %s\n", lineNum, token,"数值"); 
  439.              
  440.             preType = TokenType.Special; 
  441.             this.compressedStr.append(token); 
  442.              
  443.         } else if(token.startsWith("\"")) { 
  444.             System.out.printf("%4d: %s --- %s\n", lineNum, token,"字符串"); 
  445.              
  446.             token = (preType == TokenType.KeyWord ? " " : "") + token; 
  447.             this.compressedStr.append(token); 
  448.             preType = TokenType.Str; 
  449.              
  450.         } else { 
  451.             System.out.printf("%4d: %s --- %s\n", lineNum, token,"标识符"); 
  452.              
  453.             token = (preType == TokenType.KeyWord ? " " : "") + token; 
  454.             this.compressedStr.append(token); 
  455.             preType = TokenType.ID; 
  456.         }  
  457.     } 
  458.      
  459.     /** 
  460.      * 打印并将被压缩后的源代码写入新的文件中 
  461.      */ 
  462.     public void printCompressedFile(String compressedFile) throws Exception { 
  463.         System.out.println(this.compressedStr); 
  464.         // 创建压缩后的文件输出流 
  465.         this.compressedFileWriter = new PrintWriter( 
  466.                 new FileOutputStream(new File(compressedFile))); 
  467.         // 写入到新的文件 
  468.         this.compressedFileWriter.write(new String(this.compressedStr)); 
  469.         this.compressedFileWriter.flush(); 
  470.     } 
  471.      
  472.     /** 
  473.      * 测试 
  474.      */ 
  475.     public static void main(String[] args) throws Exception { 
  476.         Scanner_2 scanner = new Scanner_2(); 
  477.          
  478.         System.out.println("扫描未压缩源代码文件 >> "); 
  479.         scanner.scanning("cppSrc.cpp"); 
  480.          
  481.         System.out.println("\n压缩之后的源代码 >> ");        
  482.         scanner.printCompressedFile("afterCompressed.cpp"); 
  483.          
  484.         System.out.println("\n扫描压缩后的源代码文件 >> "); 
  485.         scanner.scanning("afterCompressed.cpp"); 
  486.     }