你好 Antlr
2007 年 3 月 7 日
在說過 HelloSablecc 之後,我也想試試 Antlr,這是 Java 空間的另一種編譯器編譯器。如同該條目,這只是要讓 Antlr 使用非常簡單的「hello world」樣式語法。
與 SableCC 一樣,Antlr 是一種編譯器編譯器工具。它已經存在一段時間,而且我碰過幾個使用它的專案。與 SableCC(和老牌的 lex/yacc 組合)不同,它使用 LL 語法產生遞迴下降剖析器。編譯器專家喜歡爭論 LL 或 LALR 哪個比較好,我不會在此介入這場辯論。
我的簡單案例是剖析一個包含類似這樣項目清單的檔案
item camera item laser
每一行都有「item」關鍵字,後面接著一個單字作為項目的名稱。我將把每個項目物件載入一個設定檔物件,讓它們全部集中在一起。
public class Configuration { private Map<String, Item> items = new HashMap<String, Item>(); public Item getItem(String key) { return items.get(key); } public void addItem(Item arg) { items.put(arg.getName(), arg); } public class Item { private String name; public Item(String name) { this.name = name; }
以下是使用上面顯示的檔案進行測試。
@Test public void readTwoItems() { Reader input = null; try { input = new FileReader("catalog.txt"); } catch (FileNotFoundException e) { throw new RuntimeException(e); } Configuration config = ParserCommand.parse(input); assertNotNull(config.getItem("camera")); assertNotNull(config.getItem("laser")); assertEquals(2, config.getItems().size()); }
和之前一樣,使用編譯器編譯器來解決這個問題很愚蠢,但這就像在主控台上列印「hello world」一樣。由於我總是使用新的環境撰寫「hello world」,所以我喜歡寫一些非常簡單的東西,只是為了確保我可以在開始用它做任何實際的事情之前,讓所有事情都能正常運作。
使用像這樣的編譯器編譯器會遇到的麻煩之一是,它讓建置過程變得更複雜。我必須在語法檔案上執行 antlr,才能為剖析器建立 java 類別,然後將它們包含在編譯中。因此,現在是時候再次與 ant 奮戰了,以下是 ant 目標
<property name = "dir.parser" value = "${dir.gen}/parser"/> <path id = "path.antlr"> <fileset dir = "${dir.lib}"> <include name = "antlr*.jar"/> <include name = "stringtemplate*.jar"/> </fileset> </path> <target name = "gen" > <mkdir dir="${dir.parser}"/> <java classname="org.antlr.Tool" classpathref="path.antlr" fork = "true" failonerror="true"> <arg value="-o"/> <arg value="${dir.parser}"/> <arg value="Catalog.g"/> </java> </target>
這會在 gen
目錄中產生程式碼。這樣一來,產生的程式碼便會與我自行撰寫的原始碼分開。另一個目標則會執行編譯
<property name = "dir.build" value = "classes/production/antlrLair"/> <target name = "compile" depends = "gen"> <mkdir dir="${dir.build}"/> <javac destdir="${dir.build}" classpathref="classpath"> <src path = "src"/> <src path = "${dir.gen}"/> <src path = "test"/> </javac> </target>
然後,我可以使用最終目標來執行測試。
<target name = "test" depends="compile"> <junit haltonfailure = "on"> <formatter type="brief"/> <classpath refid = "classpath"/> <batchtest todir="${dir.build}" > <fileset dir = "test" includes = "**/*Test.java"/> </batchtest> </junit> </target>
Antlr 使用語法檔案 Catalog.g
。語法檔案會定義語法中的產生式,以及剖析器在遇到產生式時執行的動作。語法檔案也會定義詞法分析器(如果需要,您可以將它們分開)。在這個意義上,Antlr 比 SableCC 更傳統(且更靈活)。SableCC 不允許動作,而是會產生剖析樹或 AST,並使用 Java 執行這些動作。Antlr 允許任意動作,或者它支援以與 SableCC 相同的方式建立樹。(Antlr 也會使用語法檔案來執行樹狀結構。)由於我正在建立一個簡單的項目網域模型和設定檔,因此我將放棄樹狀結構建立,並在我的動作中執行所有工作。
我將分批檢閱這個檔案,並在檢閱時提供說明。我從語法標題開始
grammar Catalog;
Antlr 支援多個點,您可以在這些點將程式碼注入到產生的剖析器中(而不是產生 SableCC 所做的超類別)。我將套件宣告和匯入放入標題中。
@header{ package parser; import model.*; } @lexer::header { package parser; }
下一個程式碼注入是將程式碼放入產生的類別主體中。這基本上會將成員新增到類別中,因此這是指令名稱的由來。
@members { public Configuration result = new Configuration(); }
現在,我可以進入語法的產生式。我將由上而下執行這個動作,因為它是一個由上而下的剖析器。我從說明目錄包含多個 item
子句,後接檔案結尾開始。
catalog : item* EOF;
接下來,我將項目子句定義為字面字串 'item',後接字串。
item : 'item' name=STRING {result.addItem(new Item ($name.text));};
在這裡,我也放入了動作,也就是在模型中建立一個新的項目,其中名稱設定為字串的值。大括號內的程式碼是 Java 程式碼,它會在辨識該術語後新增到剖析器中。我可以在產生式中命名元素,然後在動作中參照這些元素。在這裡,我已將字串命名為 'name',這在語境中是有意義的,即使它會造成書寫上的不便。
最後的產生式會定義字串和空白的詞法分析器元素。
STRING : ('a'..'z' | 'A'..'Z')+ ; WS : (' ' |'\t' | '\r' | '\n')+ {skip();} ;
空白的動作是跳過(忽略)它。
有幾件事讓 Antlr 比 SableCC 更容易使用。Antlr 有個稱為 AntlrWorks 的不錯 IDE,可以插入 IntelliJ。此工具會提供語法元素的語法突顯和完成、繪製語法的語法圖,並允許您輸入要剖析的測試片段,顯示產生的剖析樹。這是個非常有用的工具,可讓您了解剖析器在做什麼。不過,動作內部的程式碼並沒有突顯/完成,這是一個可以理解的痛點。
Antlr 的另一個好功能是,有一個 不錯的書 正在製作中。這本書詳細說明了此工具的工作方式,以及語言和編譯器原理的有用背景知識。它假設您正在使用一種完整的語言,而且您會產生程式碼,這對於 DSL 工作來說不一定如此。不過,隨著我深入探究,它提供的詳細資訊看來會非常有價值。
如果您想填充模型,Antlr 的動作看起來像是一個比較容易的選擇,我不確定中間剖析樹或 AST 在這裡有多大的用處。再次強調,進一步的調查將能讓我更了解。語言越複雜,擁有中間樹狀表示就越有用。我喜歡 Antlr 的彈性,它允許您使用動作或樹狀結構來進行轉換。
即使是這個簡單的範例,我還是不可避免地遇到了問題。我最大的阻礙是我最初將目錄項目定義為 catalog : item*;
,也就是沒有 EOF
。然後我感到困惑,因為剖析器在取得虛假輸入(例如 xitem foo
)時沒有指出錯誤。Antlr 和 AntlrWorks 之間的不一致性沒有幫助(後者確實顯示錯誤,而且舊版的 AntlrWorks 處理空白的方式也不同)。
(另一個大麻煩是讓 ant 和 JUnit 正常運作。我不想去想多年來我花費了多少時間嘗試診斷類別路徑問題,特別是出現 臭名昭著 的「Ant 找不到任務或此任務依賴的類別」訊息時。)