在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的教程多而杂,但是很多都...嗯,"规避了最关键的部分",我应该把遇到的坑的解决方法写出来.