解析器恐懼
2008 年 5 月 20 日
最近我與許多人討論 特定領域語言,而我對外部 DSL 常見的反應是,撰寫解析器很困難。的確,將 XML 用作外部 DSL 的載體語法的其中一個理由是「你可以免費取得解析器」。這與我的經驗不符 - 我認為解析器比大多數人想像的容易撰寫,而且它們並不比解析 XML 難。
我甚至有證據。好吧,其實只有一個案例,但我還是會引用它,因為它支持我的論點。當我為我的書撰寫 入門範例 時,我開發了多個外部 DSL 來填充一個簡單的狀態機。其中一個使用 XML(將其用作入門藥物),另一個是自訂語法,我使用 Antlr 來解析。撰寫完全解析這些格式的程式碼所花的時間大約相同。
儘管你可以免費取得 XML 解析器(我使用 Elliotte Rusty Harold 出色的 XOM 架構),但 XML 解析器的輸出實際上是以 XML DOM 形式存在的解析樹。為了對其執行任何有用的操作,你必須進一步處理它。我使用 DSL 的做法是將它們建立在明確的 語意模型 周圍,因此在此情況下,解析的真正輸出是一個已填充的狀態機模型。為了執行此操作,我必須撰寫程式碼,讓它遍歷 XML DOM。這並不容易,特別是因為我可以使用 XPath 表達式挑選出我感興趣的 DOM 部分。的確,我根本沒有遍歷 DOM 樹 - 對於我感興趣的每一件事,我都有方法發出 XPath 查詢,遍歷結果節點並填充狀態機模型。
因此,XML 處理很容易,但並非不存在 - 大約一百行程式碼。我花了幾個小時。我已經一段時間沒有使用 XOM 了,因此需要一些熟悉,但這是一個非常容易學習和使用的函式庫。
Antlr 處理方式非常類似。Antlr 有一個符號,讓您可以在語法檔案中放入一些簡單規則,以填入 AST。處理 AST 和填入語義模型的程式碼與 XML 程式碼非常類似 - 查詢樹中的正確節點,然後處理它們。包括語法檔案,產生的程式碼約為 250 行,但花了我大約相同時間來撰寫。在此之前,我已熟悉大部分的 Antlr,曾使用過幾次,但我之前實際上並未用過樹狀結構建立。 (如果您有興趣,可以在我正在撰寫的書中找到此範例的說明。)
儘管我對剖析器產生器的探索讓我習慣於它們比許多人想像的容易撰寫許多,但當我發現它實際上並不比 XML 案例慢時,我感到很驚訝。在一個更仔細控制的範例中,我仍然預期它會花費更長的時間,因為我第二個才做 Antlr 範例,而任何程式設計師都知道,事情總是會在第二次執行時快很多。儘管如此,差異遠低於許多人似乎預期的 - 當「剖析器」這個詞似乎意指「太複雜」。
我不能否認,肯定有一個學習曲線可以習慣剖析器產生器。您必須習慣語法檔案以及它們如何與程式碼範例互動。您可以使用不同的策略(我目前稱為樹狀結構建立、內嵌翻譯和內嵌詮釋)。您還必須思考自訂語法句法,這涉及比思考在 XML 中將某個東西設為屬性或元素更多的決策。但那個曲線並未真正那麼高。現代工具讓它更容易許多。Antlr 是我目前的預設選擇,它附帶一個非常好的 IDE,有助於探索語法表達式並了解它們如何剖析成 AST。但一旦您習慣了一個剖析器產生器的工作方式,要學習其他剖析器產生器就不難了。
那麼,為什麼對撰寫 DSL 的剖析器有種不合理的恐懼?我想這歸結於兩個主要原因。
- 您沒有在大學上過編譯器課程,因此認為剖析器很可怕。
- 您有在大學上過編譯器課程,因此確信剖析器很可怕。
第一個很容易理解,人們自然會對他們不了解的事物感到緊張。第二個原因是有趣的原因。這歸結於人們在大學中如何遇到剖析。剖析通常只在編譯器課程中教授,其中背景是剖析一個完整的通用語言。剖析通用語言比剖析特定領域語言困難得多,如果沒有別的原因,那是因為語法會大得多,而且通常包含您可以使用 DSL 避免的難題。
這個問題會因鼓勵將剖析與輸出處理和程式碼產生糾纏在一起的程式碼而變得更複雜。對我來說,保持一切順利的關鍵是使用語義模型,這樣您的剖析器就不會做除了填入那個模型之外的事。大部分時間,我只要執行那個語義模型,就像執行任何其他 OO 架構一樣,就可以完成我需要做的事。大部分時間,我不需要進行程式碼產生,而當我進行程式碼產生時,我會根據語義模型進行程式碼產生,因此它與剖析器無關。我認為,如果您在語法中放入了程式碼產生陳述式,事情會變得過於緊密結合。
若要讓人們有效率地使用外部 DSL,必須以與教授如何剖析通用語言截然不同的方式進行教授。語言和語言中腳本的規模都很小,會改變人們通常對剖析有的許多疑慮。除非你真的需要,否則避免產生程式碼可以消除許多複雜性。使用明確的語義模型可以將步驟分隔成更易於處理的區塊。
當然,問題在於沒有多少寫作遵循這些準則。(這是我花這麼多時間研究它的原因之一。)你很難找到任何關於剖析器產生器工具的說明文件。當你真的找到一些非常好的說明文件(例如 Terence Parr 的 Antlr 書籍)時,它通常還是以通用語言心態撰寫的。別誤會我的意思,我發現 Antlr 書籍非常有幫助(這是我將 Antlr 視為剖析器產生器的預設選擇的主要原因),但我相信它假設要剖析的是通用語言,而不是特定領域語言,這使得它比實際上更難以理解。
不過,這一切的好處在於你仍然可以克服這個學習曲線。如果你尚未嘗試使用剖析器產生器,我絕對建議你試試看。嘗試撰寫你自己的簡單 DSL。開始時不用擔心產生程式碼,只要像平常一樣建立一個領域模型,然後讓 DSL 填入它即可。從一些真的非常愚蠢的東西開始(就像我使用 HelloAntlr 所做的那樣),然後逐步從那裡開始。研究一些使用 DSL 的開放原始碼專案,看看它們做了什麼。
我們嘗試要做的,是介紹在編譯器中經常使用,但比編譯器更通用的工具給一群只將這些工具與編譯器聯想在一起的受眾,因為這是他們一直以來所受的教導方式。
-- Rebecca Parsons