在Gradle中集成Javacc

有時我們會希望在項目中使用一些腳本語言、DSL或特殊格式的配置文件什麼的,雖然已經有一些現成的方案,比如使用Java內置的JS引擎,或者使用LuaJ、Groovy之類的外部庫,但這些不是局限性略大,就是需要附帶龐大的庫,比如FML就附帶了一個Scala運行時庫(以及一個編譯器!),而實際上MC現在又有多少個用Scala寫的Mod呢?看Kotlin最近勢頭這麼火,估計過幾天他們就得附帶一個Kotlin庫了吧...言歸正題,這個時候我們就需要一個自行設計的腳本語言或者配置文件格式了,然而手寫一個Parser確實有一定難度,不過好在市面上有一類神奇的東西:"編譯器編譯器"

從字面含義來解釋的話,"編譯器編譯器"就是一個用於編譯編譯器的編譯器,實際上這是一種Parser生成器,根據輸入的語法模板來生成一套對應的Parser.如果把語法文件看成一種代碼的話,這種生成器就相當於將代碼編譯成一個可運行的Parser,故此將其成為"編譯器編譯器"還是挺恰當的.Javacc就是這樣的一個工具,它的名稱大概是向Yacc(Yet another compiler compiler)致敬,它可以將一個語法模板編譯成一段能夠解析這種語法格式的Java程序,換句話說就是生成一個Parser的源碼,除了Javacc以外,還有不少可用在Java中的編譯器編譯器,比如ANTLR、SableCC和Parboiled等,這三者都有各自的特點,這裡就不一一論述了.

之前已經提到Javacc的工作原理是生成Parser的源碼,這意味着Javacc不可能像一個類庫一樣,直接加入項目依賴當中就可以拿來使用,事實上它是一個像Javac一樣的獨立程序,或者說是一個預處理器,在程序構建前執行一遍,根據語法模板生成Parser源碼.理論上可以使用Gradle提供的運行可執行文件的Task來實現,不過這裡可以使用一個現成的方案,John Martel製作了一個自動運行Javacc的Gradle插件JavaccPlugin,使用方式是在buildscript的dependencies中加一條:

classpath 'ca.coglinc:javacc-gradle-plugin:2.3.1'

然後在用到Javacc的項目中加入:

apply plugin: 'ca.coglinc.javacc'

插件會自動將src/main/javacc中的.jj的文件生成為Java源碼並存儲在build/generated/javacc文件夾中.此外要與IDE協作的話還要做一些修改,Eclipse有一個挺不錯的JavaCC插件,可以提供語法高亮和在IDE中即時編譯.jj文件,然而它只支持原地生成Parser的代碼,平時這樣做沒什麼,但在Gradle構建時會由於同時存在兩處重複的代碼而編譯失敗,因此我在項目中也將JavaccPlugin配置為原地生成代碼.

sourceSets {
	main {
		java {
			srcDir "src/main/javacc"
		}
	}
}

ext {
	jjPackage = 'src/main/javacc/[包名]'
}

compileJavacc {
	inputDirectory = file(jjPackage)
	outputDirectory = file(jjPackage)
}
	
compileJavacc.doLast {
	delete file(jjPackage + '/tmp')
}

eclipseClasspath.dependsOn("compileJavacc")

其實我還在糾結要不要碰Parser這個坑,畢竟這不是我的強項嘛(笑),也許後文我會更新一些關於Javacc的使用之類的,說起來Javacc的教程多而雜,但是很多都...嗯,"規避了最關鍵的部分",我應該把遇到的坑的解決方法寫出來.