Java实现C的语法分析器(预测分析法)

来源:互联网 发布:淘宝店的店铺介绍 编辑:程序博客网 时间:2024/05/16 17:00
 

Java实现C的语法分析器(预测分析法)

分类: 编译原理 1780人阅读 评论(38) 收藏 举报
javaLL1编译原理语法分析预测分析

         在上一次词法分析的基础之上,我完成了我的C语言的语法分析器。这次选择的是用Java来实现,采用了自顶向下的分析方法,其思想是根据输入token串的最左推导,试图根据现在的输入字符来判断用哪个产生式来进行推导。

         使用LL(1)分析法的问题就是对文法的要求较高,要求消除回溯了左递归,以致于后来在写文法的时候遇到各种麻烦,做了各种消除(本来想偷个懒,不通过程序直接手动解决这个问题的,结果还是耗费了不少体力)。

         预测分析法的关键在于预测分析表的构造。这就要求求出每个变量的FIRST集和FOLLOW集,并且根据这来求出每个产生式的SELECT集。所以只要在顺利的理解了求解非终结符的FIRST集和FOLLOW集的算法之后,语法分析的实现其实已经胜利在望了。


图1.求FIRST集的算法

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 初始化First集 
  3.  *  
  4.  * @param expList 
  5.  * @param mFirst 
  6.  */  
  7. public static List<String> initFirst(List<Grammar> expList,  
  8.         List<String> mFirst) {  
  9.     List<String> right = null;  
  10.     for (int i = 0; i < expList.size(); i++) { // 初始化FIRST集  
  11.         right = expList.get(i).getRightPart();  
  12.         if (right.size() > 0) {  
  13.             if (terminalSymbolMap.containsKey(right.get(0))) { // 若右部表达式的第一位为终结符则加入nts的FIRST集  
  14.                 mFirst.add(right.get(0));  
  15.             }  
  16.         }  
  17.     }  
  18.     return mFirst;  
  19. }  


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 求非终结符的FIRST集 
  3.      *  
  4.      * @param nts 
  5.      *            非终结符 
  6.      * @return FIRST集 
  7.      */  
  8.     public static List<String> FIRST(NonTerminalSymbol nts) {  
  9.         List<Grammar> expList = getMyExp(nts.getValue());  
  10.         List<String> mFirst = new ArrayList<String>();  
  11.         List<String> right = null;  
  12.   
  13.         mFirst = initFirst(expList, mFirst);// 初始化工作  
  14.   
  15.         for (int i = 0; i < expList.size(); i++) {  
  16.             right = expList.get(i).getRightPart();  
  17.             if (right.size() > 0) {  
  18.                 if (nonTerminalSymbolMap.containsKey(right.get(0))) {// 若右部表达式的第一位为非终结符则递归  
  19.                     combineList(mFirst,  
  20.                             FIRST(new NonTerminalSymbol(right.get(0))));  
  21.   
  22.                     int j = 0;  
  23.                     while (j < right.size() - 1 && isEmptyExp(right.get(j))) { // 若X→Y1…Yn∈P,且Y1...Yi-1⇒*ε,  
  24.                         combineList(mFirst,  
  25.                                 FIRST(new NonTerminalSymbol(right.get(j + 1))));  
  26.                         j++;  
  27.                     }  
  28.                 }  
  29.             }  
  30.         }  
  31.         if (mFirst.contains("$"))  
  32.             mFirst.remove("$");  
  33.         combineList(nts.getFirst(), mFirst);  
  34.         return mFirst;  
  35.     }  

图2.求FOLLOW集的算法

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 求一个文法串的FIRST集 
  3.  *  
  4.  * @param right 
  5.  *            文法产生式的右部 
  6.  * @return FIRST集 
  7.  */  
  8. public static List<String> getNTSStringFirst(List<String> right) {  
  9.     List<String> mFirst = new ArrayList<String>();  
  10.   
  11.     if (right.size() > 0) { // 初始化,FIRST(α)= FIRST(X1);  
  12.         String curString = right.get(0);  
  13.         if (terminalSymbolMap.get(curString) != null)  
  14.             combineList(mFirst, terminalSymbolMap.get(right.get(0))  
  15.                     .getFirst());  
  16.         else {  
  17.             combineList(mFirst, FIRST(new NonTerminalSymbol(curString)));  
  18.         }  
  19.     }  
  20.   
  21.     if (right.size() > 0) {  
  22.         if (nonTerminalSymbolMap.get(right.get(0)) != null) { // 第一个符号为非终结符  
  23.             if (right.size() == 1) { // 直接将这个非终结符的first集加入right的first集  
  24.                 combineList(mFirst, nonTerminalSymbolMap.get(right.get(0))  
  25.                         .getFirst());  
  26.             } else if (right.size() > 1) {  
  27.                 int j = 0;  
  28.                 while (j < right.size() - 1 && isEmptyExp(right.get(j))) { // 若Xk→ε,FIRST(α)=FIRST(α)∪FIRST(Xk+1)  
  29.                     String tString = right.get(j + 1);  
  30.                     if (nonTerminalSymbolMap.get(tString) != null) {  
  31.                         combineList(mFirst, FIRST(new NonTerminalSymbol(  
  32.                                 tString)));  
  33.                     } else {  
  34.                         combineList(mFirst, terminalSymbolMap.get(tString)  
  35.                                 .getFirst());  
  36.                     }  
  37.                     j++;  
  38.                 }  
  39.             }  
  40.         }  
  41.     }  
  42.     return mFirst;  
  43. }  
  44.   
  45. /** 
  46.  * 求非终结符的FOLLOW集 
  47.  *  
  48.  * @param nts 
  49.  *            非终结符 
  50.  * @return FOLLOW集 
  51.  */  
  52. public static void FOLLOW() {  
  53.     for (int i = 0; i < grammarList.size(); i++) {  
  54.         String left = grammarList.get(i).getLeftPart();  
  55.         List<String> right = grammarList.get(i).getRightPart();  
  56.   
  57.         for (int j = 0; j < right.size(); j++) {  
  58.             String cur = right.get(j);  
  59.   
  60.             if (terminalSymbolMap.containsKey(cur)) // 若是终结符则忽略  
  61.                 continue;  
  62.   
  63.             if (j < right.size() - 1) {  
  64.                 List<String> nList = new ArrayList<String>();  
  65.                 for (int m = j + 1; m < right.size(); m++)  
  66.                     nList.add(right.get(m));  
  67.                 List<String> mfirst = getNTSStringFirst(nList);  
  68.                 for (String str : mfirst) {  
  69.                     if (!nonTerminalSymbolMap.get(cur).getFollowList()  
  70.                             .contains(str)) {  
  71.                         combineList(nonTerminalSymbolMap.get(cur)  
  72.                                 .getFollowList(), mfirst);  
  73.                         whetherChanged = true;  
  74.                         break;  
  75.                     }  
  76.                 }  
  77.   
  78.                 boolean isNull = true// 判断A→αBβ,且β⇒*ε,其中β能否为空  
  79.                 for (int k = 0; k < nList.size(); k++) {  
  80.                     if (!isEmptyExp(nList.get(k))) {  
  81.                         isNull = false;  
  82.                         break;  
  83.                     }  
  84.                 }  
  85.                 if (isNull == true) {  
  86.                     for (String str : nonTerminalSymbolMap.get(left)  
  87.                             .getFollowList()) {  
  88.                         if (!nonTerminalSymbolMap.get(cur).getFollowList()  
  89.                                 .contains(str)) {  
  90.                             combineList(nonTerminalSymbolMap.get(cur)  
  91.                                     .getFollowList(), nonTerminalSymbolMap  
  92.                                     .get(left).getFollowList());  
  93.                             whetherChanged = true;  
  94.                             break;  
  95.                         }  
  96.                     }  
  97.                 }  
  98.             } else if (j == right.size() - 1) { // 若该非终结符位于右部表达式最后一个字符  
  99.                 List<String> mfollow = nonTerminalSymbolMap.get(left)  
  100.                         .getFollowList();  
  101.                 for (String str : mfollow) {  
  102.                     if (!nonTerminalSymbolMap.get(cur).getFollowList()  
  103.                             .contains(str)) {  
  104.                         combineList(nonTerminalSymbolMap.get(cur)  
  105.                                 .getFollowList(), mfollow);  
  106.                         whetherChanged = true;  
  107.                         break;  
  108.                     }  
  109.                 }  
  110.             }  
  111.         }  
  112.     }  
  113. }  

         上面的两张图很详细的表述了如何求解FIRST集和FOLLOW集的算法,我正是根据这两个算法将其实现的。在实现的过程中,我引入了一个缺陷,我的FIRST求解用到了递归求解,而这样可以正常运行的条件必须是使用的文法不存在左递归,不然就会产生StackOverFlow的错误。因此在对FOLLOW集求解的时候,为了避免文法存在右递归,不能再使用同求FIRST集类似的递归来求解了。我采用了每执行一遍求FOLLOW集的方法,将所有的非终结符的FOLLOW集都求解出来,如果其中有一个非终结符的FOLLOW集发生了改变,则再执行一遍该方法。根据FIRST集和FOLLOW集就可以很快捷的求出SELECT集,并构造出预测分析表。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 求一个文法表达式的SELECT集 
  3.  *  
  4.  * @param g 
  5.  *            一个文法表达式 
  6.  * @return SELECT集 
  7.  */  
  8. public static List<String> SELECT(Grammar g) {  
  9.     List<String> mselect = new ArrayList<String>();  
  10.     if (g.getRightPart().size() > 0) {  
  11.         if (g.getRightPart().get(0).equals("$")) { // 含有空表达式  
  12.             mselect = nonTerminalSymbolMap.get(g.getLeftPart())  
  13.                     .getFollowList();  
  14.             return mselect;  
  15.         } else {  
  16.             mselect = getNTSStringFirst(g.getRightPart());  
  17.             boolean isNull = true;  
  18.             for (int i = 0; i < g.getRightPart().size(); i++) { // 如果右部可以推出空  
  19.                 if (!isEmptyExp(g.getRightPart().get(i))) {  
  20.                     isNull = false;  
  21.                     break;  
  22.                 }  
  23.             }  
  24.             if (isNull) {  
  25.                 combineList(mselect,  
  26.                         nonTerminalSymbolMap.get(g.getLeftPart())  
  27.                                 .getFollowList());  
  28.             }  
  29.             return mselect;  
  30.         }  
  31.     }  
  32.     return null;  
  33. }  

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 构造预测分析表 
  3.      */  
  4.     public static void getAnalysisTable() {  
  5.         List<String> synString = new ArrayList<String>();  
  6.         synString.add(SYN);  
  7.   
  8.         for (int i = 0; i < grammarList.size(); i++) {  
  9.             Grammar grammar = grammarList.get(i);  
  10.             if (analysisTable.get(grammar.getLeftPart()) != null) { // 如果多个文法表达式的左部相同,则将其合并  
  11.                 for (String sel : grammar.getSelect()) {  
  12.                     analysisTable.get(grammar.getLeftPart()).put(sel,  
  13.                             grammar.getRightPart());  
  14.                 }  
  15.             } else {  
  16.                 HashMap<String, List<String>> inMap = new HashMap<String, List<String>>();  
  17.                 for (String sel : grammar.getSelect())  
  18.                     inMap.put(sel, grammar.getRightPart());  
  19.                 analysisTable.put(grammar.getLeftPart(), inMap);  
  20.             }  
  21.             NonTerminalSymbol nts = nonTerminalSymbolMap.get(grammar  
  22.                     .getLeftPart());  
  23.             List<String> msynList = nts.getSynList();  
  24.             for (int j = 0; j < msynList.size(); j++) {  
  25.                 analysisTable.get(grammar.getLeftPart()).put(msynList.get(j), // 将同步记号集加入分析表中  
  26.                         synString);  
  27.             }  
  28.         }  
  29.     }  


         下面简要的说一下我的数据结构。我对终结符、非终结符、文法产生式和单词封装到了实体类中。将符号的值与其FIRST集、FOLLOW集或SELECT集进行了关联。


图3.主要展现实体类的成员变量

         程序中所有的终结符和非终结符分别保存在一个一维的哈希表中,其值作为HashMap的key,而其值所对应的对象作为HashMap的的value;所有经过程序处理的仅包含一个产生式右部的产生式保存在一个LIST中,LIST中装载着文法所对应的Grammar对象;预测分析表保存在一个二维的哈希表中,这个HashMap的key是某一个非终结符,value是其对应的一个一维HashMap,这个一维HashMap的key是终结符,value是某非终结符遇到某终结符时所对应的操作(一个产生式的右部或是同步记号)。

         错误处理主要是依赖于同步记号集合,所以在语法分析中错误处理的关键就在于同步记号记号的构造。我的非终结符A的同步记号集合是将A的FIRST集、FOLLOW集、其高层结构的FIRST集组合而成的。在语法分析的时候,如果输入缓冲区中是终结符a,栈顶为非终结符,在预测分析表中对应的表项为空,则忽略终结符a;在预测分析表中对应的表项是synch(定义这样一个字符串来表示同步记号),则将当前栈顶的非终结符弹出,继续分析;如果栈顶的终结符和输入符合a不匹配,则将栈顶终结符弹出。

分析过程部分代码:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * LL(1)语法分析控制程序,输出分析树 
  3.  *  
  4.  * @param sentence 
  5.  *            分析的句子 
  6.  */  
  7. public static void myParser(List<String> sentence) {  
  8.     Stack<String> stack = new Stack<String>();  
  9.     int index = 0;  
  10.     String curCharacter = null;  
  11.     stack.push("#");  
  12.     stack.push("program");  
  13.   
  14.     while (!stack.peek().equals("#")) {  
  15.         if (index < sentence.size()) // 注意下标,否则越界  
  16.             curCharacter = sentence.get(index);  
  17.         if (terminalSymbolMap.containsKey(stack.peek())  
  18.                 || stack.peek().equals("#")) {  
  19.             if (stack.peek().equals(curCharacter)) {  
  20.                 if (!stack.peek().equals("#")) {  
  21.                     stack.pop();  
  22.                     index++;  
  23.                 }  
  24.             } else {  
  25.                 System.err.println("当前栈顶符号" + stack.pop() + "与"  
  26.                         + curCharacter + "不匹配");  
  27.             }  
  28.         } else {  
  29.             List<String> exp = analysisTable.get(stack.peek()).get(  
  30.                     curCharacter);  
  31.             if (exp != null) {  
  32.                 if (exp.get(0).equals(SYN)) {  
  33.                     System.err.println("遇到SYNCH,从栈顶弹出非终结符" + stack.pop());  
  34.                 } else {  
  35.                     System.out.println(stack.peek() + "->"  
  36.                             + ListToString(exp));  
  37.                     stack.pop();  
  38.   
  39.                     for (int j = exp.size() - 1; j > -1; j--) {  
  40.                         if (!exp.get(j).equals("$")  
  41.                                 && !exp.get(j).equals(SYN))  
  42.                             stack.push(exp.get(j));  
  43.                     }  
  44.                 }  
  45.             } else {  
  46.                 if (index < sentence.size()-1){  
  47.                     System.err.println("忽略" + curCharacter);  
  48.                     index++;  
  49.                 }else {  
  50.                     System.err.println("语法错误,缺少 '}' !");  
  51.                     break;  
  52.                 }  
  53.             }  
  54.         }  
  55.     }  
  56. }  
0 0
原创粉丝点击