Groovy は AST 変換の実装用に AST を生成する DSL を提供しています。ところがこの DSL は複雑で習得が困難です。次のような GString の AST を生成したいとします:
"Hi, $name."その場合、DSL はこうなります。こんなもの構文と同じ数覚えられません:
gString 'Hi, $name.', {
strings {
constant 'Hi, '
constant '.'
}
values {
variable 'name'
}
}
ソースコードから直接生成することもできますが、コンパイラによるチェックの恩恵を受けられないですし (これについては Joachim Baumann が説明しています)、DSL よりパフォーマンスが悪いです。次のベンチマークを見てください:
@Grab('com.googlecode.gbench:gbench:0.2.2')
import gbench.BenchmarkBuilder
import org.codehaus.groovy.ast.builder.AstBuilder
def benchmarks = new BenchmarkBuilder().run {
'DSL to AST' {
new AstBuilder().buildFromSpec {
gString 'Hi, $name.', {
strings {
constant 'Hi, '
constant '.'
}
values {
variable 'name'
}
}
}
}
'Code to AST' {
new AstBuilder().buildFromString('"Hi, $name"')
}
}
benchmarks.prettyPrint()
user system cpu real
DSL to AST 0 0 0 339918
Code to AST 0 0 0 2076590
というわけで、DSL を実装コードやテストコードを見ながら書く羽目になるのですが、これが大変な作業になることは簡単に想像してもらえるはずです。どうすれば楽に書けるのか
この問題を解決する為に DSL をコードから自動生成してくれるライブラリを作りました。 AstSpecBuilder と名付けたこのライブラリはここで公開しています。使い方はとても簡単、build メソッドに馴染みのあるコードを文字列で渡すだけです:
import astspecbuilder.*
def spec = new AstSpecBuilder().build('"Hi, $name."')
def expectedSpec = '''\
block {
returnStatement {
gString 'Hi, $name.', {
strings {
constant 'Hi, '
constant '.'
}
values {
variable 'name'
}
}
}
}
'''
assert expectedSpec == spec
AST からも生成できます。というより実は上のメソッドは次のコードのショートカットに過ぎません:import astspecbuilder.*
def ast = new AstBuilder.buildFromString('"Hi, $name."')
def spec = new AstSpecBuilder().build(ast)
インデントはデフォルトではスペース4つですが、変更するオプションが付いてます。次のようにすると DSL がタブ1つでインデントされます:
def spec = new AstSpecBuilder(indent: '\t').build('"foo"')
def expectedSpec = '''\
block {
\treturnStatement {
\t\tconstant 'foo'
\t}
}
'''
assert expectedSpec == spec
No comments:
Post a Comment