一個完整的語法分析、詞法分析例子——Universal Pasrser
需求:使用者用formal notation指定語法、詞法,然後可以匹配相應的文字。用法類似正則表示式,只需給出formal notation,不需要為每一種格式的文字單獨寫匹配器。
formal notation主要是3個部分:
1)BNF 列表 table:給出上下文無關文法的產生式規則,以及所有的符號(終端和非終端)
2)起始符號 start
3)終端符號Regular Expression Table:用RE給出每個終端符號的匹配規則
其中BNF目前只接受left factor過的,去左遞迴的上下文無關文法。
關於空格的處理有3種方式,本案採用第2種:
1)作為文法的一部分:
<SPACE> --> S*
<E> --> <SPACE> <E> <SPACE> + <SPACE> <E> <SPACE>
2)作為terminal symbol的一部分
對每個terminal symbol的regular expression做處理,兩頭加上匹配空白的規則 re = "\\s+" + re + "\\s+"
3)如果不是用正則表示式匹配終端符號,而是自己寫term(TOKEN type)函式
對於空格,skip掉就可以了
文法匹配就是 top down的 recursive descend 演算法,
1)對於當前符號,如果是終結符號,到終結符號RE表找到對應的RE做匹配,匹配成功要把指標移動到後一個位置。
2)如果是非終結符號,嘗試每個production, 直到某個production成功。(left factor 保證只有一個production是可行的),注意epsilon production放到最後。
下一步工作:
1)支援沒有進行left factor的文法。現在是一個production失敗了才回溯嘗試下一個production。一般的情況是,一個符號V即使走某條production成功匹配了,但有可能導致V後面的符號無法匹配成功,這時候也要回溯,嘗試V的別的production。
2)自動消除左遞迴,這樣使用者只需要自然的寫BNF就行了
package excercise; import java.util.*; import java.util.regex.*; class CFG { private Map<String, List<String[]>> BNF; private Map<String, String> termRegex; private String start, text; private int i = 0; public CFG(Map<String, List<String[]>> productions, String start, Map<String, String> termRegex) { this.BNF = productions; this.termRegex = termRegex; this.start = start; } public boolean recognize(String text) { this.text = text; return match(start) && i == text.length(); } private boolean match(String V) { if (V.isEmpty()) return true; //epsilon List<String[]> production = BNF.get(V); if (production == null) { //no production for this symbol, should be terminal symbol String re = termRegex.get(V); //get the regex for this terminal symbol if (re == null) throw new RuntimeException("invalid CFG."); re = "\\s*" + re + "\\s*"; Pattern pattern = Pattern.compile(re); Matcher matcher = pattern.matcher(text); if (matcher.find(i) && matcher.start() == i) { i = matcher.end(); return true; } return false; } //try each production. if one matches, succeed, or recover the pointer and try next. int save = i; for (String[] p : production) { boolean flag = true; i = save; for (String T : p) { if (!match(T)) { flag = false; break; } } if (flag) { System.out.println(V + "->" + String.join(" ", p)); return true; } } return false; } public static void main(String[] args) { //1) E -> TE' //2) E'-> +TE' | -TE' | e //3) T -> FT' //3) T'-> *FT' | /FT' | e //4) F -> int | (E) //specify the BNF table, should be left factored, left recursion removed Map<String, List<String[]>> BNF = new HashMap<String, List<String[]>>(); List<String[]> pE = new ArrayList<String[]>(); pE.add(new String[]{"T", "EA"}); BNF.put("E", pE); List<String[]> pEA = new ArrayList<String[]>(); pEA.add(new String[]{"+", "T", "EA"}); pEA.add(new String[]{"-", "T", "EA"}); pEA.add(new String[]{""}); BNF.put("EA", pEA); List<String[]> pT = new ArrayList<String[]>(); pT.add(new String[]{"F", "TA"}); BNF.put("T", pT); List<String[]> pTA = new ArrayList<String[]>(); pTA.add(new String[]{"*", "F", "TA"}); pTA.add(new String[]{"/", "F", "TA"}); pTA.add(new String[]{""}); BNF.put("TA", pTA); List<String[]> pF = new ArrayList<String[]>(); pF.add(new String[]{"INT"}); pF.add(new String[]{"(", "E", ")"}); BNF.put("F", pF); //specify terminal symbol regular expression table Map<String, String> termRegex = new HashMap<String, String>(); termRegex.put("+", "\\+"); termRegex.put("-", "\\-"); termRegex.put("*", "\\*"); termRegex.put("/", "/"); termRegex.put("(", "\\("); termRegex.put(")", "\\)"); termRegex.put("INT", "[0-9]"); CFG cfg = new CFG(BNF, "E", termRegex); boolean b = cfg.recognize("(1 + 1)*2"); System.out.println(b); } }